#!/usr/bin/perl # If anyone feels the aching need to do some Perl, please feel free to # rewrite this *mess*. # # Also, I'm not sure the crow's feet are correct, or at least the code # might be making unsafe assumptions. # # Tom Gidden, 17-Oct-05 use DBI; use Data::Dumper; use Carp qw|verbose|; use Getopt::Std; our %opts; getopts('D:H:U:P:LChamgsl:c:t:w:', \%opts); our $dbname_default = 'Database'; our $dbhost_default = 'localhost'; our $dbuser_default = 'ro'; our $dbpass_default = 'ro'; if($opts{'h'}) { print < Column font size (10) -t Table name font size (12) -l Label font size (8) -m Include magnets for tables -g Include magnets for columns -s Include shadows -w Table width (100) -a Include all tables in database -D Database name (default: $dbname_default) -H Database host (default: $dbhost_default) -U Database user (default: $dbuser_default) -P Database password (default: $dbpass_default) Example: mysql2graffle -c 10 -t 20 -l 8 -d Database table1 table2 ... tableN EOF exit; } our $dbname = $opts{'D'} ? $opts{'D'} : $dbname_default; our $dbhost = $opts{'H'} ? $opts{'H'} : $dbhost_default; our $dbuser = $opts{'U'} ? $opts{'U'} : $dbuser_default; our $dbpass = $opts{'P'} ? $opts{'P'} : $dbpass_default; our $cs = $opts{'c'} || 10; our $ts = $opts{'t'} || 12; our $ls = $opts{'l'} || 8; our $shadows = $opts{'s'}; our $nolabels = $opts{'L'}; our $nocolumns = $opts{'C'}; our $magnets = $opts{'m'}; our $colmags = $opts{'g'}; our $tw = $opts{'w'} || 100; our $rh = $nocolumns ? $ts : $cs; our $alltables = $opts{'a'}; $dbh = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost", $dbuser, $dbpass); die "Can't Connect to MySQL: $!" unless($dbh); $dbh->do('SET SESSION wait_timeout=3600'); our %unique; our %indexes; our %part_of_primkey; our %tids; our %table_columns; our @tables; our $x=0; our $column_links = 0; if($alltables) { @tables = (map { s/\`//gso; s/^.+?\.//gso; $_; } $dbh->tables); } else { @tables = @ARGV; shift @tables; } # Load lookup tables { my $id = 1; foreach my $table (@tables) { my $q = $dbh->prepare('show columns from '.$table); $q->execute or die $dbh->errstr; # $tabs{$id} = $table; $tids{$table} = ++$id; while($row = $q->fetchrow_hashref) { $tids{$table.'.'.$$row{Field}} = ++$id; $table_columns{$table}{$$row{Field}} = 1; push @{$table_column_list{$table}}, $$row{Field}; $isnullable{$table}{$$row{Field}} = $$row{Null} eq 'YES' ? 1 : 0; } $q->finish; $q = $dbh->prepare('show indexes from '.$table); $q->execute or die $dbh->errstr; my $ind = ''; my $lastrow; my @cols; if($row = $q->fetchrow_hashref) { do { if($ind ne $$row{Key_name}) { if($ind) { my $key = join(',', sort @cols); foreach $col (@cols) { $part_of_primkey{$table}{$col} = 1 if($ind eq 'PRIMARY'); } $unique{$table}{$key} = 1 if($$row{Non_unique}==0); $indexes{$table}{$key} = $$row{Null}eq'YES' ? 1 : 2; } @cols = (); $lastrow = $row; $ind = $$row{Key_name}; } my $col = $$row{Column_name}; $col =~ s/\`//gso; push @cols, $col; } while($row = $q->fetchrow_hashref); if($ind) { my $key = join(',', sort @cols); foreach $col (@cols) { $part_of_primkey{$table}{$col} = 1 if($ind eq 'PRIMARY'); } $unique{$table}{$key} = 1 if($$lastrow{Non_unique}==0); $indexes{$table}{$key} = $$lastrow{Null}eq'YES' ? 1 : 2; } } @cols = keys %{$part_of_primkey{$table}}; if(scalar @cols == 1) { $unique{$table}{$cols[0]} = 1; } } } print '',"\n"; print '',"\n"; print "\nGraphicsList\n\n"; # Iterate tables { foreach my $table (@tables) { print table_xml($table); } } print "\n\n\n"; sub table_xml($) { my ($table) = @_; my $ret = ''; $ret .= "\n"; $ret .= "ClassGroup\n"; $ret .= "ID".$tids{$table}."\n"; $ret .= "Magnets\n\n{0,-1}\n{0,1}\n\n" if($magnets); $ret .= "Graphics\n\n"; $x += $tw*1.5; my $y = 0; # Draw table header $ret .= "\n"; $ret .= "ClassShapedGraphic\n"; $ret .= "Bounds{{$x, $y}, {$tw, $rh}}\n"; $ret .= "ShapeRectangle\n"; $ret .= "Style\n\nshadow\n\nDrawsNO\n\n\n"; $ret .= "Text\n\nText{\\rtf1\\f0\\fs$ts $table}\n\n"; $ret .= "\n"; $y += $rh; unless($nocolumns) { # Draw columns foreach my $col (@{$table_column_list{$table}}) { my $fnt = '\\rtf1\\f0\\fs'.$cs; $fnt .= '\\b' if($unique{$table}{$col}); $fnt .= '\\ul' if($part_of_primkey{$table}{$col}); $fnt .= '\\i' if($isnullable{$table}{$col}); $ret .= "\n"; if($colmags) { $ret .= "ID".$tids{$table.'.'.$col}."\n"; $ret .= "AllowConnectionsYES\n"; $ret .= "Magnets\n\n{-1,0}\n{1,0}\n\n"; } else { $ret .= "AllowConnectionsNO\n"; } $ret .= "ClassShapedGraphic\n"; $ret .= "Bounds{{$x, $y}, {$tw, $rh}}\n"; $ret .= "Style\n\nshadow\n\nDrawsNO\n\nstroke\n\nDrawsNO\n\n\n"; $ret .= "Text\n\nText{$fnt $col}\n\n"; $ret .= "\n"; $y += $rh; } } $ret .= "\n"; $ret .= "ClassShapedGraphic\n"; $ret .= "Bounds{{$x, 0}, {$tw, $y}}\n"; $ret .= "ShapeRectangle\n"; $ret .= "Style\n\nshadow\n\nDraws".($shadows?'YES':'NO')."\n\n\n"; $ret .= "\n"; $ret .= "\n\n"; # # Do keys # { # foreach my $from_col (@{$table_column_list{$table}}) { # next unless($from_col =~ m/^(.+)_id$/); # my $to_table; # my $to_table_id; # my $to_col = $from_col; # next unless($to_table_id = $tids{$to_table = $1}); # next if($to_table eq $table); # next unless($table_columns{$to_table}{$to_col}); # This stuff is for MySQL databases with foreign key constraints. { my $ct; my $q = $dbh->prepare('SHOW CREATE TABLE '.$table) or die $dbh->errstr; $q->execute; my @row = $q->fetchrow_array; die $dbh->errstr unless($ct = pop @row); while($ct =~ s/CONSTRAINT \`[^\`]+\` FOREIGN KEY \(\s*([^\)]+)\s*\) REFERENCES `([^\`]+)` \(\s*([^\)]+)\s*\)//osi) { my ($from_col, $to_table, $to_col) = ($1,$2,$3); $from_col =~ s/\s*\`\s*//gso; $to_col =~ s/\s*\`\s*//gso; $to_table =~ s/\s*\`\s*//gso; $from_col = join(',', sort (split(/\s*\,\s*/, $from_col))); $to_col = join(',', sort (split(/\s*\,\s*/, $to_col))); my $head; my $tail; if(!$column_links or $from_col =~ m/\,/so) { $tail = $tids{$table}; } else { $tail = $tids{"$table.$from_col"}; } if(!$column_links or $to_col =~ m/\,/so) { $head = $tids{$to_table}; } else { $head = $tids{"$to_table.$to_col"}; } my $col; if($from_col eq $to_col) { $col = $from_col; } else { $col = "$from_col = $to_col"; } my $headtype = 0; my $tailtype = 0; if($unique{$to_table}{$to_col}) { $headtype = 'Bar'; } else { $headtype = 'Crow'; } # Work out if to can be zeroed (if from can be null) XXX my $fromnull = 0; foreach my $fc (split(/\,/, $from_col)) { $fromnull = 1 if($isnullable{$table}{$fc}); } $tailtype = 'CrowBall'; if($fromnull) { if($headtype eq 'Crow') { $headtype = 'CrowBall'; } else { $headtype = 'BarBall'; } } else { if($headtype eq 'Crow') { $headtype = 'CrowsFeet'; } else { $headtype = 'DoubleBar'; } } # #DoubleBar # #BarBall # #CrowBall # #CrowBar # #CrowsFeet $ret .= "\n"; $ret .= "ClassLineGraphic\n"; $ret .= "Head\n\nID$head\n\n"; $ret .= "Tail\n\nID$tail\n\n"; $ret .= "Labels\n\n\nOrientation2\nLabel\n\nAlign0\nText{\\rtf1\\f0\\fs$ls $col}\n\nLabelVisibleYES\n\n\n" unless($nolabels); $ret .= "OrthogonalBarAutomatic\n"; $ret .= "Style\n\nstroke\n\nCap0\nHeadArrow$headtype\nHopLines\nJoin0\nLineType2\nTailArrow$tailtype\n\n\n"; $ret .= "\n"; } } return $ret; }