File Coverage

blib/lib/Autodia/Handler/Torque.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             ################################################################
2             # AutoDIA - Automatic Dia XML. (C)Copyright 2001 A Trevena #
3             # #
4             # AutoDIA comes with ABSOLUTELY NO WARRANTY; see COPYING file #
5             # This is free software, and you are welcome to redistribute #
6             # it under certain conditions; see COPYING file for details #
7             # #
8             # ---- #
9             # 03.05.04 - J. Gilbreath - Vanderbilt University #
10             # - Modified to handle tables with only one column #
11             # as well as those without any keys (primary or #
12             # foreign). #
13             # - Foreign keys, indexed columns, and unique columns were #
14             # also added as operations on each table. #
15             # - Primary key support was changed to add a operation for #
16             # each primary key instead of grouping all of them #
17             # under one operation. #
18             # - Relationship code was enhanced to handle foreign key #
19             # relationships to the same table without throwing #
20             # an exception during diagram construction. #
21             # - Finally, _subParse function was trimmed and sub functions #
22             # broken out for individual portions of table #
23             # processing. #
24             # - TODO : Add support for onUpdate and onDelete for foreign #
25             # keys (maybe a comment on the operation?) #
26             # ---- #
27             ################################################################
28             package Autodia::Handler::Torque;
29              
30             require Exporter;
31              
32 1     1   1948 use strict;
  1         4  
  1         34  
33 1     1   382 use XML::Simple;
  0            
  0            
34             ## added for debugging - jg
35             use Data::Dumper;
36              
37             use vars qw($VERSION @ISA @EXPORT);
38             use Autodia::Handler;
39              
40             @ISA = qw(Autodia::Handler Exporter);
41              
42             use Autodia::Diagram;
43              
44             #---------------------------------------------------------------
45              
46             #####################
47             # Constructor Methods
48              
49             # new inherited from Autodia::Handler
50              
51             #------------------------------------------------------------------------
52             # Access Methods
53              
54             # parse_file inherited from Autodia::Handler
55              
56             #-----------------------------------------------------------------------------
57             # Internal Methods
58              
59             # _initialise inherited from Autodia::Handler
60              
61             sub _parse {
62             my $self = shift;
63             my $fh = shift;
64             my $filename = shift;
65              
66             my $Diagram = $self->{Diagram};
67              
68             my $xml = XMLin(join('',<$fh>));
69              
70             #print Dumper($xml->{table});
71             my %tables = ();
72             my @relationships = ();
73              
74             # process tables
75              
76             foreach my $tablename (sort keys %{$xml->{'table'}}) {
77             #print "Processing table $tablename\n";
78              
79             my $Class = Autodia::Diagram::Class->new($tablename);
80             $Diagram->add_class($Class);
81              
82             # Orignially primary keys were placed into a HASH and all appeared as one
83             # operation on the class (table). This was replaced to generate an operation
84             # for each Primary Key to reduce the size of the table width for tables with
85             # many primary keys.
86             # In addition, foreign keys, index columns, and unique columns were added as
87             # operations as well. -jg
88             # primary key(s)
89             #my $primary_key = { name=>'Primary Key', type=>'pk', Params=>[], visibility=>0, };
90              
91             $tables{$tablename} = $Class;
92              
93             # process column(s) and primary key(s)
94             _processColumns($Class, $xml, $tablename);
95              
96             # process foreign key(s)
97             _processForeignKeys($self, $Class, $xml, $tablename);
98              
99             # process index(es)
100             _processIndexes($Class, $xml, $tablename);
101              
102             # process unique column(s)
103             _processUniqueColumns($Class, $xml, $tablename);
104              
105             } # end foreach table
106             } # end _parse
107              
108             ####
109             # Adds a Primary Key as an operation of the given class
110             ####
111             sub _addPKOperation {
112             my ($localClass , $localColumn) = @_;
113             $localClass->add_operation({name=>"Primary Key", type=>'pk', Params=>[{Name=>$localColumn, Type=>''}],
114             visibility=>0 });
115             }
116              
117             ####
118             # Adds a Foreign Key as an operation of the given class
119             ####
120             sub _addFKOperation {
121             my ($localClass , $localFK, $localFKTable) = @_;
122             $localClass->add_operation({name=>"Foreign Key", type=>'fk', Params=>[{Name=>$localFK, Type=>$localFKTable}],
123             visibility=>0 });
124             }
125              
126             ####
127             # Adds an Indexed Column as an operation of the given class
128             ####
129             sub _addIndexOperation {
130             my ($localClass , $localColumn) = @_;
131             $localClass->add_operation({name=>"Indexed Column", type=>'ic', Params=>[{Name=>$localColumn, Type=>''}],
132             visibility=>0 });
133             }
134              
135             ####
136             # Adds a Unique Column as an operation of the given class
137             ####
138             sub _addUniqueOperation {
139             my ($localClass , $localColumn) = @_;
140             $localClass->add_operation({name=>"Unique Column", type=>'uc', Params=>[{Name=>$localColumn, Type=>''}],
141             visibility=>0 });
142             }
143              
144              
145             ####
146             # Builds a Relationship for the given Class based on the given Foreign Key
147             # reference and adds it to the Diagram
148             ####
149             sub _buildFKRelationship {
150             my ($localSelf, $localClass, $localFK) = @_;
151             # create foreign key table or get it if already present
152             my $Superclass = Autodia::Diagram::Superclass->new($localFK);
153             my $exists_already = $localSelf->{Diagram}->add_superclass($Superclass);
154             if (ref $exists_already) {
155             $Superclass = $exists_already;
156             }
157              
158             # create new relationship
159             my $Relationship = Autodia::Diagram::Inheritance->new($localClass, $Superclass);
160             # add Relationship to superclass
161             $Superclass->add_inheritance($Relationship);
162             # add Relationship to class
163             $localClass->add_inheritance($Relationship);
164             # add Relationship to diagram
165             $localSelf->{Diagram}->add_inheritance($Relationship);
166             }
167              
168             ####
169             # Constructs a Foreign Key compound string from the given HASH
170             ####
171             sub _constructForeignKey {
172             my %fkHash = @_;
173             return "(l=".$fkHash{"local"}." : f=". $fkHash{"foreign"}.") ";
174             }
175              
176             ####
177             # Constructs a Type for the column based on the given HASH
178             ####
179             sub _constructType {
180             my %typeHash = @_;
181             if (exists $typeHash{"size"}) {
182             return $typeHash{"type"}."(".$typeHash{"size"}.")";
183             } else {
184             return $typeHash{"type"};
185             }
186             }
187              
188             ####
189             # Processes the Columns using the given XML, Class, and tablename
190             #
191             # The processing takes into account that depending on the quantity of columns a table
192             # has, the reference in the XML will map differently. The HASH will key off of the
193             # keyword "name" if the table has a single column. The key to the HASH will be the
194             # column name if the table has more than one column.
195             ####
196             sub _processColumns {
197             my ($localClass, $localXML, $localTablename) = @_;
198             my %columnHash;
199             foreach my $column (keys %{$localXML ->{'table'}{$localTablename}{'column'}}) {
200             no strict 'refs';
201             %columnHash = %{$localXML ->{'table'}{$localTablename}{'column'}};
202             if (exists $columnHash{"name"}) {
203             # this is a table with one column
204             my $columnName = $columnHash{"name"};
205             #if ($column eq "name") {
206             # print "adding column $columnName to $localTablename\n";
207             #}
208              
209             $localClass->add_attribute({
210             name => $columnName,
211             visibility => 0,
212             type => _constructType(%columnHash),
213             });
214             if ($column eq "primaryKey") {
215             # add each primary key as a different operation to avoid wide
216             # class diagrams
217             _addPKOperation($localClass, $columnName);
218             }
219              
220             } else {
221             # this is a table with multiple columns in which case
222             # the key is the column name repopulate hash one deep
223             %columnHash = %{$localXML ->{'table'}{$localTablename}{'column'}{$column}};
224              
225             #print "adding column $column to $localTablename\n";
226              
227             $localClass->add_attribute({
228             name => $column,
229             visibility => 0,
230             type => _constructType(%columnHash),
231             });
232              
233             if (exists $columnHash{"primaryKey"}) {
234             # add each primary key as a different operation to avoid wide
235             # class diagrams
236             _addPKOperation($localClass, $column);
237             }
238             }
239             }
240             } # end processColumns
241              
242             ####
243             # Processes the Foreign Keys using the given XML , Class, self, and tablename
244             #
245             # Again, XML will parse differently based on the quantity of foreign keys. It will be a
246             # HASH if only one foreign key exists for the table. It will be an ARRAY if there is more
247             # than one. In addtion, a local relationship HASH holds the names of the tables in which
248             # relationships were made so only one relationship is constructed for tables with many
249             # foreign keys to the same table.
250             ####
251             sub _processForeignKeys {
252             my ($localSelf, $localClass, $localXML, $localTablename) = @_;
253             if (exists $localXML->{'table'}{$localTablename}{'foreign-key'}) {
254             no strict 'refs';
255             if (ref($localXML->{'table'}{$localTablename}{'foreign-key'}) eq 'HASH' ) {
256             # this table has only one foreign-key
257             #print "$localTablename has only one foreign key \n";
258             #print Dumper($localXML ->{'table'}{$localTablename}{'foreign-key'});
259             my %fKeyHash = (%{$localXML->{'table'}{$localTablename}{'foreign-key'}});
260             _buildFKRelationship($localSelf, $localClass, $fKeyHash{"foreignTable"});
261             if (exists $localXML ->{'table'}{$localTablename}{'foreign-key'}{'reference'}) {
262             _addFKOperation($localClass,
263             _constructForeignKey(%{$localXML ->{'table'}{$localTablename}{'foreign-key'}{'reference'}}),
264             $fKeyHash{"foreignTable"});
265             }
266             } else {
267             # this table has more than one foreign-key
268             #print "$localTablename has more than one foreign key \n";
269             #print Dumper($localXML->{table}{$localTablename}{'foreign-key'});
270              
271             # hash that holds the foreign key table names
272             # this is used to avoid a division by zero error if a reference is made to the
273             # same table more than once. -jg
274             my %relMade;
275             # the foreign table name
276             my $foreignTableName = "";
277             foreach my $fKeyArray (@{$localXML->{'table'}{$localTablename}{'foreign-key'}}) {
278             #print Dumper($fKeyArray);
279             $foreignTableName = $fKeyArray->{'foreignTable'};
280             #print "processing foreign key $foreignTableName \n";
281             if (!exists ($relMade{"$foreignTableName"})) {
282             _buildFKRelationship($localSelf, $localClass, $foreignTableName);
283             # add it to the hash of foreign table names
284             $relMade{$foreignTableName} = $foreignTableName;
285             }
286             if (defined $fKeyArray->{'reference'}) {
287             _addFKOperation($localClass,
288             _constructForeignKey(%{$fKeyArray->{'reference'}}),
289             $foreignTableName);
290             }
291             }
292             }
293             }
294             } # end processForeignKeys
295              
296             ####
297             # Processes the indexes using the given Class, XML, and tablename
298             #
299             # The processing here is complex due to the fact that the Torque schema DTD allows
300             # a table to have multiple nodes defined each with one to many
301             # nodes as well.
302             ####
303             sub _processIndexes {
304             my ($localClass, $localXML, $localTablename) = @_;
305             if (exists $localXML -> {'table'}{$localTablename}{'index'}) {
306             no strict 'refs';
307             if (ref ($localXML->{'table'}{$localTablename}{'index'}) eq 'HASH') {
308             # so this is a HASH; however, it could be that the HASH contains only one
309             # index column or many just depending on the parse.
310             #print Dumper($localXML->{'table'}{$localTablename}{'index'});
311             my %indexHash = %{$localXML->{'table'}{$localTablename}{'index'}{'index-column'}};
312             if (exists $indexHash{"name"}) {
313             # this is indeed a single index column
314             #print "$localTablename has only one index-column \n";
315             _addIndexOperation($localClass, $indexHash{"name"});
316             }
317             else {
318             foreach my $indexKey (keys %{$localXML->{'table'}{$localTablename}{'index'}{'index-column'}}) {
319             # the key is the actual name of the column
320             _addIndexOperation($localClass, $indexKey);
321             }
322             }
323             }
324             else {
325             foreach my $indexArray (@{$localXML->{'table'}{$localTablename}{'index'}}) {
326             #print "Indexed columns for $localTablename are: \n";
327             #print Dumper($indexArray);
328             foreach my $indexKey (keys %{$indexArray->{'index-column'}}) {
329             if ($indexKey eq "name") {
330             # this is an instance of a table with multiple index nodes, one with
331             # only one index-column and the other with many index-column nodes
332             # so add the name of the column
333             _addIndexOperation($localClass, $indexArray->{'index-column'}{'name'});
334             } else {
335             # the key is the actual name of the column
336             _addIndexOperation($localClass, $indexKey);
337             }
338             } # end foreach in keys
339             } # end foreach in array
340             } # end else
341             } # end if exists
342             } # end processIndexes
343              
344             ####
345             # Process the unique columns of the table using the given Class, XML, and tablename
346             #
347             # Just like index columns, the processing here is complex due to the fact that
348             # the Torque schema DTD allows a table to have multiple nodes defined each with
349             # one to many nodes as well.
350             ####
351             sub _processUniqueColumns {
352             my ($localClass, $localXML, $localTablename) = @_;
353             if (exists $localXML -> {'table'}{$localTablename}{'unique'}) {
354             no strict 'refs';
355             if (ref ($localXML->{'table'}{$localTablename}{'unique'}) eq 'HASH') {
356             # so this is a HASH; however, it could be that the HASH contains only one
357             # unique column or many just depending on the parse.
358             #print Dumper($localXML->{'table'}{$localTablename}{'unique'});
359             my %uniqueHash = %{$localXML->{'table'}{$localTablename}{'unique'}{'unique-column'}};
360             if (exists $uniqueHash{"name"}) {
361             # this is indeed a single unique column
362             #print "$localTablename has only one unique-column \n";
363             _addUniqueOperation($localClass, $uniqueHash{"name"});
364             }
365             else {
366             foreach my $uniqueKey (keys %{$localXML->{'table'}{$localTablename}{'unique'}{'unique-column'}}) {
367             # the key is the actual name of the column
368             _addUniqueOperation($localClass, $uniqueKey);
369             }
370             }
371             }
372             else {
373             # this is any array of unique columns
374             foreach my $uniqueArray (@{$localXML->{'table'}{$localTablename}{'unique'}}) {
375             #print "Unique columns for $localTablename are: \n";
376             #print Dumper($uniqueArray);
377             foreach my $uniqueKey (keys %{$uniqueArray->{'unique-column'}}) {
378             if ($uniqueKey eq "name") {
379             # this is an instance of a table with multiple unique nodes, one with
380             # only one unique-column and the other with many unique-column nodes
381             # so add the name of the column
382             _addUniqueOperation($localClass, $uniqueArray->{'unique-column'}{'name'});
383             } else {
384             # the key is the actual name of the column
385             _addUniqueOperation($localClass, $uniqueKey);
386             }
387             } # end foreach in keys
388             } # end foreach in array
389             } # end else
390             } # end if exists
391             }
392             1;
393              
394             ###############################################################################
395              
396             =head1 NAME
397              
398             Autodia::Handler::Torque.pm - AutoDia handler for Torque xml database schema
399              
400             =head1 INTRODUCTION
401              
402             This provides Autodia with the ability to read Torque Database Schema files, allowing you to convert them via the Diagram Export methods to images (using GraphViz and VCG) or html/xml using custom templates or to Dia.
403              
404             =head1 SYNOPSIS
405              
406             use Autodia::Handler::Torque;
407              
408             my $handler = Autodia::Handler::dia->New(\%Config);
409              
410             $handler->Parse(filename); # where filename includes full or relative path.
411              
412             =head1 Description
413              
414             The Torque handler will parse the xml file using XML::Simple and populating the diagram object with class, superclass, and relationships representing tables and relationships.
415              
416             The Torque handler is registered in the Autodia.pm module, which contains a hash of language names and the name of their respective language.
417              
418             An example Torque database schema is shown here - its actually a rather nice format apart from the Java studlyCaps..
419              
420              
421            
422              
423            
424              
425            
426              
427            
428            
429            
430            
431              
432            
433            
434            
435              
436            
437            
438            
439            
440            
441            
442            
443              
444            
445            
446            
447            
448            
449            
450            
451              
452             =head1 METHODS
453              
454             =head2 CONSTRUCTION METHOD
455              
456             use Autodia::Handler::Torque;
457              
458             my $handler = Autodia::Handler::Torque->New(\%Config);
459             This creates a new handler using the Configuration hash to provide rules selected at the command line.
460              
461             =head2 ACCESS METHODS
462              
463             $handler->Parse(filename); # where filename includes full or relative path.
464              
465             This parses the named file and returns 1 if successful or 0 if the file could not be opened.
466              
467             =head1 SEE ALSO
468              
469             Autodia
470              
471             Torque
472              
473             Autodia::Handler
474              
475             =cut