Adding new ER diagramming producer.
[dbsrgits/SQL-Translator.git] / bin / auto-dia.pl
index feea3f3..39158d3 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 
-# $Id: auto-dia.pl,v 1.1 2003-02-14 20:29:12 kycl4rk Exp $
+# $Id: auto-dia.pl,v 1.9 2003-04-24 16:33:23 kycl4rk Exp $
 
 =head1 NAME 
 
@@ -12,164 +12,111 @@ auto-dia.pl - Automatically create a diagram from a database schema
 
   Options:
 
-    -o|--output  Output file name (default STDOUT)
-    -i|--image   Output image type (default PNG)
-    -t|--title   Title to give schema
-    -c|--cols    Number of columns
+    -o|--output        Output file name (default STDOUT)
+    -i|--image         Output image type ("png" or "jpeg," default "png")
+    -t|--title         Title to give schema
+    -c|--cols          Number of columns
+    -n|--no-lines      Don't draw lines
+    -f|--font-size     Font size ("small," "medium," "large," or "huge,"
+                       default "medium")
+    --color            Add colors
+    --show-fk-only     Only show fields that act as primary 
+                       or foreign keys
+
+    --natural-join     Perform natural joins
+    --natural-join-pk  Perform natural joins from primary keys only
+    -s|--skip          Fields to skip in natural joins
+    --debug            Print debugging information
 
 =head1 DESCRIPTION
 
 This script will create a picture of your schema.  Only the database
-driver argument is required.  If no output file name is given, then
-image will be printed to STDOUT, so you should redirect the output
-into a file.
+driver argument (for SQL::Translator) is required.  If no output file
+name is given, then image will be printed to STDOUT, so you should
+redirect the output into a file.
+
+The default action is to assume the presence of foreign key
+relationships defined via "REFERNCES" or "FOREIGN KEY" constraints on
+the tables.  If you are parsing the schema of a file that does not
+have these, you will find the natural join options helpful.  With
+natural joins, like-named fields will be considered foreign keys.
+This can prove too permissive, however, as you probably don't want a
+field called "name" to be considered a foreign key, so you could
+include it in the "skip" option, and all fields called "name" will be
+excluded from natural joins.  A more efficient method, however, might
+be to simply deduce the foriegn keys from primary keys to other fields
+named the same in other tables.  Use the "natural-join-pk" option
+to acheive this.
 
 =cut
 
 use strict;
+use Data::Dumper;
 use Getopt::Long;
-use GD;
 use Pod::Usage;
 use SQL::Translator;
 
-my $VERSION = (qw$Revision: 1.1 $)[-1];
+my $VERSION = (qw$Revision: 1.9 $)[-1];
+
+#
+# Get arguments.
+#
+my ( 
+    $out_file, $image_type, $db_driver, $title, $no_columns, 
+    $no_lines, $font_size, $add_color, $debug, $show_fk_only,
+    $natural_join, $join_pk_only, $skip_fields, $help
+);
 
-my ( $out_file, $image_type, $db_driver, $title, $no_columns );
 GetOptions(
-    'd|db=s'      => \$db_driver,
-    'o|output:s'  => \$out_file,
-    'i|image:s'   => \$image_type,
-    't|title:s'   => \$title,
-    'c|columns:i' => \$no_columns,
+    'd|db=s'          => \$db_driver,
+    'o|output:s'      => \$out_file,
+    'i|image:s'       => \$image_type,
+    't|title:s'       => \$title,
+    'c|columns:i'     => \$no_columns,
+    'n|no-lines'      => \$no_lines,
+    'f|font-size:s'   => \$font_size,
+    'color'           => \$add_color,
+    'show-fk-only'    => \$show_fk_only,
+    'natural-join'    => \$natural_join,
+    'natural-join-pk' => \$join_pk_only,
+    's|skip:s'        => \$skip_fields,
+    'debug'           => \$debug,
+    'h|help'          => \$help,
 ) or die pod2usage;
-my $file = shift @ARGV or pod2usage( -message => 'No input file' );
+my @files = @ARGV; # the create script(s) for the original db
 
+pod2usage(1) if $help;
 pod2usage( -message => "No db driver specified" ) unless $db_driver;
-$image_type = $image_type ? lc $image_type : 'png';
-$title    ||= $file;
-
-my $t    = SQL::Translator->new( parser => $db_driver, producer => 'Raw' );
-my $data = $t->translate( $file ) or die $t->error;
-my $font = gdTinyFont;
-
-my $no_tables    = scalar keys %$data;
-$no_columns    ||= sprintf( "%.0f", sqrt( $no_tables ) + .5 );
-my $no_per_col   = sprintf( "%.0f", $no_tables/$no_columns + .5 );
-warn "no per col = '$no_per_col'\n";
-
-my ( @shapes, $max_x, $max_y );
-my $orig_y      = 40;
-my ( $x, $y )   = ( 20, $orig_y );
-my $cur_col     = 1;
-my $no_this_col = 0;
-my $this_col_x  = $x;
-
-for my $table (
-    map  { $_->[1] }
-    sort { $a->[0] <=> $b->[0] }
-    map  { [ $_->{'order'}, $_ ] }
-    values %$data 
-) {
-    my $table_name = $table->{'table_name'};
-    my $top        = $y;
-    push @shapes, [ 'string', $font, $this_col_x, $y, $table_name ];
-
-    $y                   += $font->height + 2;
-    my $below_table_name  = $y;
-    $y                   += 2;
-    my $this_max_x        = $this_col_x + ($font->width * length($table_name));
-
-    my @fields = 
-        map  { $_->[1] }
-        sort { $a->[0] <=> $b->[0] }
-        map  { [ $_->{'order'}, $_ ] }
-        values %{ $table->{'fields'} };
-
-    my $pk;
-    for my $index ( @{ $table->{'indices'} || [] } ) {
-        next unless $index->{'type'} eq 'primary_key';
-        my @fields = @{ $index->{'fields'} || [] } or next;
-        $pk = $fields[0];
+pod2usage( -message => 'No input file'          ) unless @files;
+
+my $translator       =  SQL::Translator->new( 
+    from             => $db_driver,
+    to               => 'Diagram',
+    debug            => $debug || 0,
+    producer_args    => {
+        out_file     => $out_file,
+        image_type   => $image_type,
+        title        => $title,
+        no_columns   => $no_columns,
+        no_lines     => $no_lines,
+        font_size    => $font_size,
+        add_color    => $add_color,
+        show_fk_only => $show_fk_only,
+        natural_join => $natural_join,
+        join_pk_only => $join_pk_only,
+        skip_fields  => $skip_fields,
+    },
+) or die SQL::Translator->error;
+
+for my $file (@files) {
+    my $output = $translator->translate( $file ) or die
+                 "Error: " . $translator->error;
+    if ( $out_file ) {
+        print "Image written to '$out_file'.  Done.\n";
     }
-
-    my ( @fld_desc, $max_name );
-    for my $f ( @fields ) {
-        my $name = $f->{'name'} or next;
-        $name   .= ' *' if $name eq $pk;
-
-        my $size = @{ $f->{'size'} || [] } 
-            ? '(' . join( ',', @{ $f->{'size'} } ) . ')'
-            : '';
-        my $desc = join( ' ', map { $_ || () } $f->{'data_type'}, $size );
-        
-        my $nlen  = length $name;
-        $max_name = $nlen if $nlen > $max_name;
-        push @fld_desc, [ $name, $desc ];
+    else {
+        print $output;
     }
-
-    $max_name += 4;
-    for my $fld_desc ( @fld_desc ) {
-        my ( $name, $desc ) = @$fld_desc;
-        my $diff = $max_name - length $name;
-        $name   .= ' ' x $diff;
-        $desc    = $name . $desc;
-
-        push @shapes, [ 'string', $font, $this_col_x, $y, $desc ];
-        $y         += $font->height + 2;
-        my $length  = $this_col_x + ( $font->width * length( $desc ) );
-        $this_max_x = $length if $length > $this_max_x;
-    }
-
-    $this_max_x += 5;
-    push @shapes, [ 'line', $this_col_x - 5, $below_table_name, 
-        $this_max_x, $below_table_name ];
-    push @shapes, [ 
-        'rectangle', $this_col_x - 5, $top - 5, $this_max_x, $y + 5 
-    ];
-    $max_x = $this_max_x if $this_max_x > $max_x;
-    $y    += 25;
-    
-    if ( ++$no_this_col == $no_per_col ) {
-        $cur_col++;
-        $no_this_col = 0;    
-        $max_x      += 20;
-        $this_col_x  = $max_x;
-        $max_y       = $y if $y > $max_y;
-        $y           = $orig_y;
-    }
-}
-
-#
-# Add the title and signature.
-#
-my $large_font = gdLargeFont;
-my $title_len  = $large_font->width * length $title;
-push @shapes, [ 'string', $large_font, $max_x/2 - $title_len/2, 10, $title ];
-
-my $sig = "auto-dia.pl $VERSION";
-push @shapes, [ 'string', $font, $max_x/2 - $title_len/2, 10, $title ];
-
-my $gd = GD::Image->new( $max_x + 10, $max_y );
-unless ( $gd->can( $image_type ) ) {
-    die "GD can't create images of type '$image_type'\n";
-}
-my $white = $gd->colorAllocate(255,255,255);
-my $black = $gd->colorAllocate(00,00,00);
-$gd->interlaced( 'true' );
-$gd->fill( 0, 0, $white );
-for my $shape ( @shapes ) {
-    my $method = shift @$shape;
-    $gd->$method( @$shape, $black );
-}
-
-if ( $out_file ) {
-    open my $fh, ">$out_file" or die "Can't write '$out_file': $!\n";
-    print $fh $gd->$image_type;
-    close $fh;
-    print "Image written to '$out_file'.  Done.\n";
-}
-else {
-    print $gd->$image_type;
 }
 
 =pod