File Coverage

lib/Parse/Dia/SQL/Utils.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Parse::Dia::SQL::Utils;
2              
3             # $Id: Utils.pm,v 1.13 2011/02/16 10:23:11 aff Exp $
4              
5             =pod
6              
7             =head1 NAME
8              
9             Parse::Dia::SQL::Utils - Helper class for Parse::Dia::SQL.
10              
11             =head1 SYNOPSIS
12              
13             Not to be used directly.
14              
15             =head1 DESCRIPTION
16              
17             Utility functions for Parse::Dia::SQL.
18              
19             =cut
20              
21 54     54   5886 use warnings;
  54         120  
  54         1691  
22 54     54   267 use strict;
  54         116  
  54         1122  
23              
24 54     54   258 use Data::Dumper;
  54         110  
  54         2186  
25 54     54   7028 use XML::DOM;
  0            
  0            
26             use Digest::MD5 qw(md5_base64);
27              
28             use lib q{.};
29             use Parse::Dia::SQL::Logger;
30              
31             # TODO: Move constants to a separate module
32             my %MAX_NAME_LEN = (
33             default => 30,
34             db2 => 18,
35             html => 64,
36             innodb => 64,
37             mysql => 64,
38             oracle => 30,
39             postgres => 63,
40             sas => 32,
41             sqlite3 => 60,
42             sqlite3fk => 60,
43             );
44              
45             # TODO: Make this a object variable
46             my $DEFAULT_PK = [];
47              
48             =head2 set_default_pk
49              
50             Define primary key column names and types for automatic generation of
51             primary keys in tables that need them, but do not have them defined.
52              
53             =cut
54              
55             sub set_default_pk {
56             my $self = shift;
57             if ( $self->{default_pk} ) {
58             my @defPK = split /\s*:\s*/, $self->{default_pk};
59             die "Bad definition of default primary key: $self->{default_pk}\n"
60             if ( @defPK != 2 || $defPK[0] eq '' || $defPK[1] eq '' );
61             my @pkNames = split /\s*,\s*/, $defPK[0];
62             my @pkTypes = split /\s*,\s*/, $defPK[1];
63             die
64             "Number of default primary key names and types don't match in $self->{default_pk}\n"
65             if ( @pkNames != @pkTypes );
66             foreach my $i ( 0 .. $#pkNames ) {
67             my ( $name, $type ) = ( $pkNames[$i], $pkTypes[$i] );
68             die "Null primary key name in " . $self->{default_pk} if ( !$name );
69             die "Null primary key type in " . $self->{default_pk} if ( !$type );
70             push @$DEFAULT_PK, [ $name, $type, 'not null', 2, '' ];
71             }
72             }
73             return 1;
74             }
75              
76              
77             =head2 new
78              
79             The constructor. No arguments.
80              
81             =cut
82              
83             =head2 new
84              
85              
86             =cut
87              
88             sub new {
89             my ($class, %param) = @_;
90             my $self = {
91             log => undef,
92             db => $param{db} || undef,
93             default_pk => $param{default_pk} || undef,
94             loglevel => $param{loglevel} || undef,
95             };
96              
97             bless($self, $class);
98              
99             # init logger
100             my $logger = Parse::Dia::SQL::Logger::->new(loglevel => $self->{loglevel});
101             $self->{log} = $logger->get_logger(__PACKAGE__);
102              
103             return $self;
104             }
105              
106             =head2 get_node_attribute_values
107              
108             Given a node with dia:attribute nodes inside it, go through the
109             dia:attribute nodes with attribute "name='...'" and return the string
110             values
111              
112             @infosToGet is an array of strings, where the first character is the
113             data type to get, and the remaining characters are the name to parse
114             for. first character legal values are:
115              
116             a = alpha
117             9 = numeric
118             b = boolean
119              
120             example: aname, 9dollars, bkillOrNot
121              
122             =cut
123              
124             sub get_node_attribute_values {
125             my ($self, $nodeList, @infosToGet) = @_;
126              
127             # TODO check datatype of nodeList
128              
129             my ( $currentNode, $nodeAttrName, $i );
130             my %return;
131              
132             my $emptyValueString = "__undef_string__";
133             my $emptyValueNumber = "__undef_numeric__";
134             my $emptyValueBoolean = "__undef_boolean__";
135              
136             # initialise it to a bunch of empty values, this will also allow
137             # us to know which attribute name values to parse out of the
138             # dia:attribute nodelist
139             foreach my $singleInfo (@infosToGet) {
140             if ( $singleInfo =~ /^a(.+)/ ) {
141             $return{$1} = $emptyValueString;
142             }
143             elsif ( $singleInfo =~ /^9(.+)/ ) {
144             $return{$1} = $emptyValueNumber;
145             }
146             elsif ( $singleInfo =~ /^b(.+)/ ) {
147             $return{$1} = $emptyValueBoolean;
148             }
149             }
150              
151             # we're interested in everything that's a dia:attribute
152             my $attrNodeList = $nodeList->getElementsByTagName('dia:attribute');
153              
154             for ( $i = 0 ; $i < $attrNodeList->getLength ; $i++ ) {
155             $currentNode = $attrNodeList->item($i);
156             $nodeAttrName = $currentNode->getAttribute('name');
157              
158             next if ( !$nodeAttrName || !$return{$nodeAttrName} );
159              
160             # test if this is a value we're interested in and if it's currently empty
161             if ( $return{$nodeAttrName} eq $emptyValueString ) {
162              
163             # a text node gives us text
164             $return{$nodeAttrName} = $self->get_string_from_node($currentNode);
165             }
166             elsif ( $return{$nodeAttrName} eq $emptyValueNumber ) {
167             $return{$nodeAttrName} = $self->get_num_from_node($currentNode);
168             }
169             elsif ( $return{$nodeAttrName} eq $emptyValueBoolean ) {
170             $return{$nodeAttrName} = $self->get_bool_from_node($currentNode);
171             }
172             }
173              
174             {
175             no warnings q{uninitialized};
176             $self->{log}->debug( "$nodeAttrName:" . $return{$nodeAttrName} );
177             }
178              
179             # don't return some fake value for bits we didn't parse,
180             # return undef which means it wasn't there
181             foreach my $singleInfo (@infosToGet) {
182             if (
183             $singleInfo
184             && $return{$singleInfo}
185             && ( $return{$singleInfo} eq $emptyValueString
186             || $return{$singleInfo} eq $emptyValueNumber
187             || $return{$singleInfo} eq $emptyValueBoolean )
188             )
189             {
190             $return{$singleInfo} = undef;
191             }
192             }
193              
194             return %return;
195             }
196              
197              
198             =head2 get_string_from_node
199              
200             If it looks like value then
201             we will get the 'value' part out given the node is 'thingy'.
202              
203             =cut
204              
205             sub get_string_from_node {
206             my $self = shift;
207             my $node = shift;
208              
209             my $retval;
210              
211             my $stringVal;
212              
213             foreach my $stringNode ($node->getElementsByTagName('dia:string')) {
214             if ($stringVal = $stringNode->getFirstChild) {
215             $retval = $stringVal->toString;
216             } else {
217             $retval = "";
218             }
219             }
220              
221             return if !$retval; # Skip escaping if empty
222              
223             # $retval =~ s/^#?(.*)#?$/$1/g;
224             $retval =~ s/^#//;
225             $retval =~ s/#$//;
226              
227             # TODO use HTML::Entities;
228             # drTAE: also, XML files must escape certain sequences...
229             $retval =~ s/</
230             $retval =~ s/&/&/g;
231             $retval =~ s/>/>/g;
232             $retval =~ s/"/"/g;
233              
234             return $retval;
235             }
236              
237             =head2 get_value_from_object
238              
239             Given an object, node name, attribute name, attribute value, and value
240             to retrieve type, find the info and return it.
241              
242             =cut
243              
244             sub get_value_from_object {
245             my $self = shift;
246             my $object = shift;
247             my $getNodeName = shift;
248             my $getNodeAttribute = shift;
249             my $getNodeAttributeVal = shift;
250             my $infoToRetrieveType = shift;
251              
252             my $parsedValue;
253             my $currNode;
254              
255             if (
256             $currNode = $self->get_node_from_object(
257             $object, $getNodeName, $getNodeAttribute,
258             $getNodeAttributeVal
259             )
260             )
261             {
262             if ($infoToRetrieveType eq 'string') {
263             $parsedValue = $self->get_string_from_node($currNode);
264             } elsif ($infoToRetrieveType eq 'number') {
265             $parsedValue = $self->get_num_from_node($currNode);
266             } elsif ($infoToRetrieveType eq 'boolean') {
267             $parsedValue = $self->get_bool_from_node($currNode);
268             }
269              
270             return $parsedValue;
271             } else {
272             return;
273             }
274             }
275              
276             =head2 get_node_from_object
277              
278             Given an object, node name, attribute name, and attribute value,
279             return the node that has all these things.
280              
281             =cut
282              
283             sub get_node_from_object {
284             my $self = shift;
285             my $object = shift;
286             my $getNodeName = shift;
287             my $getNodeAttribute = shift;
288             my $getNodeAttributeVal = shift;
289              
290             my $k;
291             my $doneParsing;
292             my $parsedValue;
293              
294             my @nodeList = $object->getElementsByTagName($getNodeName);
295              
296             # search @nodeList for a getNodeAttribute="getNodeAttributeVal"
297             foreach my $currNode (@nodeList) {
298             if ($currNode->getNodeName eq $getNodeName) {
299             my $attrPtr = $currNode->getAttributes;
300              
301             $k = 0;
302             while (($k < $attrPtr->getLength) && !$doneParsing) {
303             $parsedValue = $attrPtr->item($k)->toString;
304             if ($parsedValue =~ /$getNodeAttribute="$getNodeAttributeVal"/) {
305             return $currNode;
306             }
307             $k++;
308             }
309             }
310             }
311              
312             # Not all nodes contain the wanted attribute
313             return;
314             }
315              
316             =head2 name_case
317              
318             Transform case for name comparisons to that of the database; leave
319             unchanged if -C (preserve case) is in effect. Only sybase is known to
320             be case sensitive.
321              
322             =cut
323              
324             sub name_case {
325             my ($self, $value) = @_;
326             return '' unless $value;
327             return $value if ($self->{opt_C} || $self->{db} eq 'sybase');
328             return lc($value); # Assumes that all other DBMSs ignore case of names!
329             }
330              
331             =head2 get_num_from_node
332              
333             Return value part of .
334              
335             =cut
336              
337             sub get_num_from_node {
338             my ($self, $node) = @_;
339             my $enumNode = shift @{$node->getElementsByTagName('dia:enum')};
340             return $enumNode->getAttribute('val');
341             }
342              
343             =head2 get_bool_from_node
344              
345             Return value part of .
346              
347             =cut
348              
349             sub get_bool_from_node {
350             my ($self, $node) = @_;
351             my $enumNode = shift @{$node->getElementsByTagName('dia:boolean')};
352             return $enumNode->getAttribute('val');
353             }
354              
355              
356             =head2 classify_multiplicity
357              
358             Look at a multiplicity descriptor and classify it as 'one' (1, or
359             1..1), 'zone' (0..1), 'many' (n..m, n..*, where n > 1, m >= n) and
360             'zmany' (0..n, 0..*, where n > 1)
361              
362             =cut
363              
364             sub classify_multiplicity {
365             my $self = shift;
366             my $multStr = shift;
367             return 'none' if ( ! $multStr );
368             $multStr =~ s/\s//g;
369             my @mult = split( /\.\./, $multStr );
370             return 'none' if ( @mult == 0 );
371             return 'undef' if ( @mult > 2 );
372             push @mult, $mult[0] if ( @mult == 1 );
373             foreach my $m (@mult) {
374             return 'undef' if ( $m !~ /^\d+$/ && $m ne '*' );
375             }
376             $mult[0] = 0 if ( $mult[0] eq '*' );
377             $mult[1] = $mult[0] + 2 if ( $mult[1] eq '*' ); # ensure $mult[1] > 1 for 0..*
378             return 'one' if ( $mult[0] == 1 && $mult[1] == 1 ); # 1..1
379             return 'zone' if ( $mult[0] == 0 && $mult[1] == 1 ); # 0..1
380             return 'many'
381             if (
382             $mult[0] >= 1 && $mult[1] > 1 # n..m, n..*,
383             && $mult[0] <= $mult[1]
384             ); # n > 0, m > 1, m >= n
385             return 'zmany' if ( $mult[0] == 0 && $mult[1] > 1 ); # 0..n, 0..*, n > 1
386             return 'undef';
387             }
388              
389              
390             # =head2 parseExtras
391              
392             # Parse the name of a Small Package that contains extra SQL clauses for
393             # the generated SQL, and add the SmallPackage text to the appropriate
394             # %tableExtras table for the type of extra clause (table, pk, index).
395              
396             # =cut
397              
398             # sub parseExtras {
399             # my $self = shift;
400             # my $type = shift;
401             # my $params = shift;
402             # my $dbText = shift;
403              
404             # my ($dbNames, $args) = split /\s*:\s*/, $params;
405             # my $warns = 0;
406              
407             # return 0 if (!$args);
408              
409             # $args =~ s/\s//g;
410             # $args =~ s/^[^(]*\(//;
411             # $args =~ s/\)$//;
412              
413             # my @args = split /\s*,\s*/, $args;
414              
415             # if ($dbNames =~ /$opt_t/) {
416             # foreach my $arg (@args) {
417             # if (!$arg) {
418             # warn "Null parameter in $params\n";
419             # $warns++;
420             # $errors++;
421             # next;
422             # }
423              
424             # if ($type =~ /^macro(.+)/) {
425             # my $when = $1;
426             # $macros{$arg} = { when => $when, sql => $dbText, used => 0 };
427             # #if ($verbose) { print "Added $when Macro $arg\n"; }
428             # } else {
429             # my $dowarn = $tableExtras{$type}->{$arg};
430             # if ($dowarn) {
431             # warn "SQL clause for $type $arg redefined from\n"
432             # . addExtraClauses('', $tableExtras{$type}->{$arg}, ' ');
433             # }
434              
435             # $tableExtras{$type}->{$arg} = { sql => $dbText, used => 0 };
436             # if ($dowarn) {
437             # warn "to\n"
438             # . addExtraClauses('', $tableExtras{$type}->{$arg}, ' ');
439             # }
440             # }
441             # }
442             # }
443              
444             # return $warns == 0;
445             # }
446              
447             =head2 attlist_from_names
448              
449             Generate a list of attributes from a comma-separated list of names by
450             looking up a class' attribute table.
451              
452             =cut
453              
454             sub attlist_from_names {
455             my $self = shift;
456             my $classLookup = shift;
457             my $nameStr = shift;
458              
459             my @names = split /\s*,\s*/, $nameStr;
460             my $attList = [];
461             foreach my $n (@names) {
462             my $a = $classLookup->{atts}{ $self->name_case($n) };
463             push @$attList, $a if ($a);
464             }
465             return $attList;
466             }
467              
468             =head2 names_from_attlist
469              
470             Generate a comma-separated list of attribute names from a list of
471             attributes.
472              
473             =cut
474              
475             sub names_from_attlist {
476             my $self = shift;
477             my $atts = shift;
478             return join ',', map { $_->[0] } @$atts;
479             }
480              
481             =head2 check_att_list_types
482              
483             Check that a list of primary key attributes has types corresponding to
484             the types in a list of foreign key attributes
485              
486             =cut
487              
488             sub check_att_list_types {
489             my $self = shift;
490             my $assocName = shift;
491             my $classPKLookup = shift;
492             my $classFKLookup = shift;
493             my $PKatts = shift;
494             my $FKatts = shift;
495             my $db = shift; # Parse::Dia::SQL::db
496              
497             if ( @$PKatts == 0 || @$PKatts != @$FKatts ) {
498             $self->{log}->warn( "Attribute list empty or lengths don't match in"
499             . " $assocName ($classPKLookup->{name},$classFKLookup->{name})");
500             return 0;
501             }
502             my $mismatches = 0;
503              
504             # The types only exist if the classes are tables, not views
505             if ( $classPKLookup->{type} eq 'table' && $classFKLookup->{type} eq 'table' )
506             {
507             foreach my $i ( 0 .. $#{$PKatts} ) {
508             my $pktype = $self->get_base_type( $self->name_case( $PKatts->[$i][1] ), $db );
509             my $fktype = $self->get_base_type( $self->name_case( $FKatts->[$i][1] ), $db );
510             if ( $pktype ne $fktype )
511             {
512             $self->{log}->warn( "Attribute types"
513             . " ($PKatts->[$i][0] is $PKatts->[$i][1],"
514             . " $FKatts->[$i][0] is $FKatts->[$i][1])"
515             . " don't match in $assocName"
516             . " ($classPKLookup->{name},$classFKLookup->{name})");
517             $mismatches++;
518             }
519             }
520             }
521              
522             return $mismatches == 0;
523             }
524              
525             =head2 get_base_type
526              
527             Check that a list of primary key attributes has types corresponding to
528             the types in a list of foreign key attributes.
529              
530             Returns base type of some DMBS specific types (eg in PostgreSQL serial
531             is integer).
532              
533             AFF note: This is better implemented in each sql formatter class.
534              
535             =cut
536              
537             sub get_base_type {
538             my $self = shift;
539             my $typeName = shift;
540             my $db = shift;
541             if ( $db eq 'postgres' ) {
542              
543             # handle PostgreSQL database type
544             if ( lc($typeName) eq 'serial' or lc($typeName) eq 'int4' or lc($typeName) eq 'int') {
545             $self->{log}->info(qq{Replaced $typeName with integer}) if $self->{log}->is_info();
546             return 'integer';
547             }
548             if ( lc($typeName) eq 'smallserial' or lc($typeName) eq 'int2' ) {
549             $self->{log}->info(qq{Replaced $typeName with smallint}) if $self->{log}->is_info();
550             return 'smallint';
551             }
552             if ( lc($typeName) eq 'bigserial' or lc($typeName) eq 'int8' ) {
553             $self->{log}->info(qq{Replaced $typeName with bigint}) if $self->{log}->is_info();
554             return 'bigint';
555             }
556              
557             return $typeName;
558             }
559             elsif ( $db eq 'templateDBMStype' ) {
560              
561             # handle this database type
562             if ( $typeName eq 'templateDatatype' ) {
563             return 'templateReturn';
564             }
565             return $typeName;
566             }
567             else {
568              
569             # all unhandled RDBMS types just return the typeName
570             return $typeName;
571             }
572             }
573              
574              
575              
576             =head2 make_name
577              
578             Generate a longer name from parts supplied. Except for the first part,
579             the first letter of each part is capitalised. If lcFirstWord is set,
580             then any initial string of capitals in the first part is made lower
581             case; otherwise the first part is left unchanged.
582              
583             Dies if $self->{db} is not set.
584              
585             The @parts_org values are save for "Desperation time" :)
586              
587             =cut
588              
589             sub make_name {
590             my ( $self, $lcFirstWord, @parts_org ) = @_;
591             my @parts = @parts_org;
592             my $namelen = undef;
593              
594             $self->{log}->logdie(q{Missing argument 'db'}) unless $self->{db};
595             $self->{log}->debug(q{Make name from parts: } . join(q{,},@parts));
596              
597             if ( exists( $MAX_NAME_LEN{$self->{db}} ) ) {
598             $namelen = $MAX_NAME_LEN{$self->{db}};
599             }
600             else {
601             $namelen = $MAX_NAME_LEN{default};
602             $self->{log}->warn(
603             "The maximum name length for $self->{db} is not set - using default $namelen");
604             }
605             $self->{log}->debug("Using namelen $namelen");
606              
607             my $len = 0;
608             foreach my $p (@parts) { $len += length($p); }
609              
610             # If maxNameLen is non-zero, then trim names down
611             if ($namelen) {
612             foreach my $p (@parts) {
613             last if ( $len <= $namelen );
614             $len -= length($p);
615              
616             # eliminate vowels
617             while ( $p =~ /(.)[aeiouAEIOU]/ ) {
618             $p =~ s/(.)[aeiouAEIOU]/$1/g;
619             }
620             while ( $p =~ /(.)\1/ ) {
621             $p =~ s/(.)\1/$1/g; # eliminate doubled letters
622             }
623             $len += length($p);
624             }
625              
626             # This part cribbed from mangleName
627             if ( $len > $namelen ) {
628             my $frac = ( $namelen - $len + @parts ) / $namelen;
629             foreach my $p (@parts) {
630             last if ( $len <= $namelen );
631             my $l = length($p);
632             my $skip = int( $frac * $l + 0.5 );
633             my $pos = int( ( $l - $skip ) / 2 + 0.5 );
634             if ($skip) {
635             $len -= $l;
636             $p = substr( $p, 0, $pos ) . substr( $p, $pos + $skip );
637             $len += length($p);
638             }
639             }
640             }
641             if ( $len > $namelen ) {
642              
643             # Desperation time!
644             my $base64 = $self->name_scramble( join '', @parts_org );
645             my $retval = substr( $base64, 0, $namelen );
646             $self->{log}->debug(qq{Made name : $retval (premature return)});
647             return $retval;
648             }
649             }
650              
651             # Remove dot, alows using postgres sql schemas - table name like shop.product
652             if ( $self->{db} eq "postgres" ) {
653             foreach my $p (@parts) {
654             $p =~ s/\.//g;
655             }
656             }
657              
658             # Handle the lowercasing of the first part of the n ame
659              
660             if ($lcFirstWord) {
661             $parts[0] =~ /([A-Z]*)(.*)/;
662             my ( $firstPart, $lastPart ) = ( $1, $2 );
663             if ($firstPart) {
664             my $recapLast = length($firstPart) > 1
665             && substr( $firstPart, -1 ) =~ /[A-Z]/
666             && $parts[0] =~ /[a-z]/;
667             $parts[0] = lc($firstPart);
668             if ($recapLast) {
669             $parts[0] = substr( $parts[0], 0, -1 ) . uc( substr( $parts[0], -1 ) );
670             }
671             }
672             else {
673             $parts[0] = '';
674             }
675             $parts[0] .= $lastPart if ($lastPart);
676             }
677             foreach my $p ( @parts[ 1 .. $#parts ] ) {
678             $p = ucfirst($p);
679             }
680              
681             $self->{log}->debug(q{Made name : } . join(q{},@parts));
682             return join '', @parts;
683             }
684              
685             =head2 name_scramble
686              
687             PSuda: Name scrambling helper function, for code which auto-generates
688             names. Takes one arg, which is string to use for md5 hashing. This
689             returns names which consist entirely of underscores and alphanumeric
690             characters, and starts with one or more alpha characters.
691              
692             =cut
693              
694             sub name_scramble {
695             my $self = shift;
696             my $base64 = md5_base64(shift);
697              
698             # Change non alphanumeric characters to underscores.
699             $base64 =~ s/[^A-Za-z0-9_]/_/g;
700              
701             # Trim off numbers at the start, so that we don't wind up with names
702             # that start with numbers. This is a problem in some instances in
703             # MySQL.
704              
705             $base64 =~ s/^[^a-zA-Z]+//g;
706             return $base64;
707             }
708              
709              
710             =head2 mangle_name
711              
712             Get a name to mangle and mangle it to the length
713             specified -- avoid too much manglification if the
714             name is only slightly long, but mangle lots if it's
715             a lot longer than the specified length.
716              
717             =cut
718              
719             sub mangle_name {
720             my $self = shift;
721             my $nameToMangle = shift;
722             my $sizeToMangleTo = shift;
723              
724             if (!(defined($nameToMangle) and defined($sizeToMangleTo) and $sizeToMangleTo =~ m/^\d+$/)){
725             $self->{log}->error("Need a string and a positive integer");
726             return;
727             }
728              
729             # if it's already okay, just return it
730             if ( length($nameToMangle) <= $sizeToMangleTo ) {
731             return $nameToMangle;
732             }
733              
734             my $newName;
735             my $base64;
736              
737             # if it's a real long name, then we mangle it plenty
738             if ( length($nameToMangle) > $sizeToMangleTo + 6 ) {
739             $base64 = $self->name_scramble($nameToMangle);
740              
741             # ensure we have enough garbage
742             while ( length($base64) < $sizeToMangleTo ) {
743             $base64 .= $self->name_scramble ( $nameToMangle . $base64 );
744             }
745              
746             $newName = substr( $base64, 0, $sizeToMangleTo );
747             }
748             elsif ( length($nameToMangle) > $sizeToMangleTo ) {
749              
750             # if it's just a little bit long, then mangle it less
751             # (remove some chars from the middle)
752             my $sizeDiv2 = $sizeToMangleTo / 2;
753             my $mangleLen = length($nameToMangle);
754              
755             $newName = substr( $nameToMangle, 0, $sizeDiv2 );
756             $newName .= substr( $nameToMangle, $mangleLen - $sizeDiv2, $sizeDiv2 );
757             }
758              
759             return $newName;
760             }
761              
762             =head2 add_default_pk
763              
764             For -p - add a default primary key to a parsed table definition
765              
766             TODO : Add a meaningful return value.
767              
768             =cut
769              
770             sub add_default_pk {
771             my $self = shift;
772             my $pkClass = shift;
773             my $pkStr = shift;
774             my $defPK = [];
775              
776             if ($pkStr) {
777              
778             # If PK names are given, then use those names rather than
779             # the default names; but take the types from the defaults
780             my @pkNames = split /\s*,\s*/, $pkStr;
781             if ( @pkNames == @$DEFAULT_PK ) {
782             foreach my $i ( 0 .. $#pkNames ) {
783             my $n = $pkNames[$i];
784             my $pkAtts = [ @{ $DEFAULT_PK->[$i] } ];
785             $pkAtts->[0] = $n;
786             push @$defPK, $pkAtts;
787             }
788             }
789             else {
790             warn
791             "Number of names in $pkStr does not match number of default PK attributes\n";
792             # $errors++;
793             }
794             }
795             else {
796              
797             # Otherwise just use the default names and types for the PK
798             $defPK = $DEFAULT_PK;
799             }
800              
801             # Add the PK attributes to the class; but complain if an attribute
802             # is already defined; The PK fields are added at the beginning of the
803             # list of attributes
804             for ( my $i = $#{$defPK} ; $i >= 0 ; $i-- ) {
805             my $pkAtts = $defPK->[$i];
806             my $n = $pkAtts->[0];
807             if ( $pkClass->{atts}{ $self->name_case($n) } ) {
808             warn
809             "In $pkClass->{name} $n is already an attribute; can't redefine it as a default primary key\n";
810             # $errors++;
811             next;
812             }
813             unshift @{ $pkClass->{attList} }, $pkAtts;
814             $pkClass->{atts}{ $self->name_case($n) } = $pkAtts;
815             }
816             $pkClass->{pk} = $defPK;
817              
818             return 1; # Explicit return is a good practice
819             }
820              
821             =head2 add_default_fk
822              
823             For -f - add missing parts of a default foreign key to a parsed table
824             definition.
825              
826             =cut
827              
828             sub add_default_fk {
829             my $self = shift;
830             my $fkClassLookup = shift;
831             my $fkStr = shift;
832             my $fkAtts = shift;
833             my $pkAtts = shift;
834             my $nullClause = shift;
835              
836             # Foreign key attributes may exist already; only create entries
837             # for those not already there
838             my @fkNames = split /\s*,\s*/, $fkStr;
839             foreach my $i ( 0 .. $#{@fkNames} ) {
840             if ( !$fkAtts->[$i]
841             || $self->name_case( $fkAtts->[$i][0] ) ne $self->name_case( $fkNames[$i] ) )
842             {
843              
844             # New FK has supplied name & supplied null clause,
845             # and its other attributes (esp type) copied from its
846             # corresponding primary key.
847             my $newFK = [
848             $fkNames[$i], $pkAtts->[$i][1],
849             $nullClause, 0, @{$pkAtts}[ 4 .. $#{ $pkAtts->[$i] } ]
850             ];
851             splice @$fkAtts, $i, 0, $newFK;
852              
853             # add the new FK column to the end of the list of column defs
854             push @{ $fkClassLookup->{attList} }, $newFK;
855             $fkClassLookup->{atts}{ $self->name_case( $fkNames[$i] ) } = $newFK;
856             }
857             }
858             return $fkAtts;
859             }
860              
861              
862             # Check that the given object and version is supported. Return true
863             # on pass, undef on fail.
864             sub _check_object_version {
865             my $self = shift;
866             my $type = shift;
867             my $version = int shift; # can be zero, can have leading zeros
868            
869             if (!$type || !defined $version) {
870             $self->{log}->error(qq{Need 2 args: type and version});
871             return;
872             }
873              
874             my %object_v = (
875             "UML - Association" => [1,2],
876             "UML - Class" => [0],
877             "UML - Component" => [0],
878             "UML - Note" => [0],
879             "UML - SmallPackage" => [0],
880             );
881              
882             $self->{log}->debug(qq{type:'$type' version:$version});
883              
884             if (!exists($object_v{$type})) {
885             $self->{log}->debug(qq{type:'$type' unknown});
886             return;
887             }
888              
889             if (! grep(/^$version$/, @{$object_v{$type}})) {
890             $self->{log}->debug(qq{type:'$type' version:$version unsupported});
891             return;
892             }
893              
894             return 1;
895             }
896              
897             # Split a type definition 'type(nn)' into 'type', '(nn)'
898             sub split_type {
899             my $self = shift;
900             my $type = shift;
901              
902             if(!$type) {
903             $self->{log}->warn("Missing type");
904             return;
905             }
906              
907             $type =~ m/^([^(]*)(\([^)]+\))?$/;
908             my ($name, $size) = ($1, $2);
909             if(!$name) {
910             $self->{log}->warn("Malformed type name $type");
911             return;
912             }
913              
914             if ($size) {
915             return ($name,$size);
916             } else {
917             return ($name);
918             }
919             }
920              
921             # =head2 parseExtras
922              
923             # Parse the name of a Small Package that contains extra SQL clauses for
924             # the generated SQL, and add the SmallPackage text to the appropriate
925             # %tableExtras table for the type of extra clause (table, pk, index).
926              
927             # =cut
928              
929             # sub parseExtras {
930             # my $self = shift;
931             # my $type = shift;
932             # my $params = shift;
933             # my $dbText = shift;
934              
935             # my ($dbNames, $args) = split /\s*:\s*/, $params;
936             # my $warns = 0;
937              
938             # return 0 if (!$args);
939              
940             # $args =~ s/\s//g;
941             # $args =~ s/^[^(]*\(//;
942             # $args =~ s/\)$//;
943              
944             # my @args = split /\s*,\s*/, $args;
945              
946             # if ($dbNames =~ /$opt_t/) {
947             # foreach my $arg (@args) {
948             # if (!$arg) {
949             # warn "Null parameter in $params\n";
950             # $warns++;
951             # $errors++;
952             # next;
953             # }
954              
955             # if ($type =~ /^macro(.+)/) {
956             # my $when = $1;
957             # $macros{$arg} = { when => $when, sql => $dbText, used => 0 };
958             # #if ($verbose) { print "Added $when Macro $arg\n"; }
959             # } else {
960             # my $dowarn = $tableExtras{$type}->{$arg};
961             # if ($dowarn) {
962             # warn "SQL clause for $type $arg redefined from\n"
963             # . addExtraClauses('', $tableExtras{$type}->{$arg}, ' ');
964             # }
965              
966             # $tableExtras{$type}->{$arg} = { sql => $dbText, used => 0 };
967             # if ($dowarn) {
968             # warn "to\n"
969             # . addExtraClauses('', $tableExtras{$type}->{$arg}, ' ');
970             # }
971             # }
972             # }
973             # }
974              
975             # return $warns == 0;
976             # }
977              
978             1;
979              
980             __END__