Reduce $Id to its normal form
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / GraphViz.pm
index 8c57208..7c18c2f 100644 (file)
@@ -1,9 +1,9 @@
 package SQL::Translator::Producer::GraphViz;
 
 # -------------------------------------------------------------------
-# $Id: GraphViz.pm,v 1.14 2007-09-26 13:20:09 schiffbruechige Exp $
+# $Id$
 # -------------------------------------------------------------------
-# Copyright (C) 2002-4 SQLFairy Authors
+# Copyright (C) 2002-2009 SQLFairy Authors
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
@@ -56,7 +56,9 @@ the GraphViz module).  It's nifty--you should try it!
 
 =item * out_file
 
-the name of the file where the graphviz graphic is to be written
+The name of the file where the resulting GraphViz output will be
+written. Alternatively an open filehandle can be supplied. If
+undefined (the default) - the result is returned as a string.
 
 =item * layout (DEFAULT: 'dot')
 
@@ -171,18 +173,47 @@ like-named argument in the make_natural_join method (see
 natural_join above) of SQL::Translator::Schema, if either
 the natural_join or join_pk_only options has a true value
 
+=item * show_indexes
+
+if set to a true value, each record will also show the indexes
+set on each table. it describes the index types along with
+which columns are included in the index. this option requires
+that show_fields is a true value as well
+
+=item * show_index_names
+
+if show_indexes is set to a true value, then the value of this
+parameter determines whether or not to print names of indexes.
+if show_index_names is false, then a list of indexed columns
+will appear below the field list. otherwise, it will be a list
+prefixed with the name of each index. it defaults to true.
+
+=item * friendly_ints
+
+if set to a true value, each integer type field will be displayed
+as a smallint, integer or bigint depending on the field's
+associated size parameter. this only applies for the 'integer'
+type (and not the lowercase 'int' type, which is assumed to be a
+32-bit integer).
+
+=item * friendly_ints_extended
+
+if set to a true value, the friendly ints displayed will take into
+account the non-standard types, 'tinyint' and 'mediumint' (which,
+as far as I am aware, is only implemented in MySQL)
+
 =back
 
 =cut
 
 use strict;
 use GraphViz;
-use Data::Dumper;
 use SQL::Translator::Schema::Constants;
 use SQL::Translator::Utils qw(debug);
+use Scalar::Util qw/openhandle/;
 
 use vars qw[ $VERSION $DEBUG ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.14 $ =~ /(\d+)\.(\d+)/;
+$VERSION = '1.99';
 $DEBUG   = 0 unless defined $DEBUG;
 
 use constant VALID_LAYOUT => {
@@ -207,31 +238,6 @@ use constant VALID_NODE_SHAPE => {
     octagon       => 1, 
 };
 
-use constant VALID_OUTPUT => {
-    canon => 1, 
-    text  => 1, 
-    ps    => 1, 
-    hpgl  => 1,
-    pcl   => 1, 
-    mif   => 1, 
-    pic   => 1, 
-    gd    => 1, 
-    gd2   => 1, 
-    gif   => 1, 
-    jpeg  => 1,
-    png   => 1, 
-    wbmp  => 1, 
-    cmap  => 1, 
-    ismap => 1, 
-    imap  => 1, 
-    vrml  => 1,
-    vtx   => 1, 
-    mp    => 1, 
-    fig   => 1, 
-    svg   => 1, 
-    plain => 1,
-};
-
 sub produce {
     my $t          = shift;
     my $schema     = $t->schema;
@@ -258,6 +264,10 @@ sub produce {
     my $show_fk_only     = $args->{'show_fk_only'};
     my $show_datatypes   = $args->{'show_datatypes'};
     my $show_sizes       = $args->{'show_sizes'};
+    my $show_indexes     = $args->{'show_indexes'};
+    my $show_index_names = defined $args->{'show_index_names'} ? $args->{'show_index_names'} : 1;
+    my $friendly_ints    = $args->{'friendly_ints'};
+    my $friendly_ints_ex = $args->{'friendly_ints_extended'};
     my $show_constraints = $args->{'show_constraints'};
     my $join_pk_only     = $args->{'join_pk_only'};
     my $skip_fields      = $args->{'skip_fields'} || '';
@@ -271,8 +281,6 @@ sub produce {
     ) if $natural_join;
 
     die "Invalid layout '$layout'" unless VALID_LAYOUT->{ $layout };
-    die "Invalid output type: '$output_type'"
-        unless VALID_OUTPUT->{ $output_type };
     die "Invalid node shape'$node_shape'" 
         unless VALID_NODE_SHAPE->{ $node_shape };
 
@@ -281,9 +289,6 @@ sub produce {
         $_ = 0 if $_ < 0;
     }
 
-    #
-    # Create GraphViz and see if we can produce the output type.
-    #
     my %args = (
         directed      => $natural_join ? 0 : 1,
         layout        => $layout,
@@ -327,41 +332,166 @@ sub produce {
         $args{'graph'}->{$key} = $val;
     }
 
-    my $gv =  GraphViz->new( %args ) or die "Can't create GraphViz object\n";
+    #
+    # Create a blank GraphViz object and see if we can produce the output type.
+    #
+    my $gv = GraphViz->new( %args ) or die "Can't create GraphViz object\n";
+    my $output_method = "as_$output_type";
+
+    # the generators are AUTOLOADed so can't use ->can ($output_method) 
+    eval { $gv->$output_method };
+    die "Invalid output type: '$output_type'" if $@;
+
 
     my %nj_registry; # for locations of fields for natural joins
     my @fk_registry; # for locations of fields for foreign keys
 
     for my $table ( $schema->get_tables ) {
-        my $table_name = $table->name;
         my @fields     = $table->get_fields;
         if ( $show_fk_only ) {
             @fields = grep { $_->is_foreign_key } @fields;
         }
 
-        my $field_str = join(
-            '\l',
-            map {
-                '-\ '
-                . $_->name
-                . ( $show_datatypes ? '\ ' . $_->data_type : '')
-                . ( $show_sizes && ! $show_datatypes ? '\ ' : '')
-                . ( $show_sizes && $_->data_type =~ /^(VAR)?CHAR2?$/i ? '(' . $_->size . ')' : '')
-                . ( $show_constraints ?
-                    ( $_->is_primary_key || $_->is_foreign_key || $_->is_unique ? '\ [' : '' )
-                    . ( $_->is_primary_key ? 'PK' : '' )
-                    . ( $_->is_primary_key && ($_->is_foreign_key || $_->is_unique) ? ',' : '' )
-                    . ( $_->is_foreign_key ? 'FK' : '' )
-                    . ( $_->is_unique && ($_->is_primary_key || $_->is_foreign_key) ? ',' : '' )
-                    . ( $_->is_unique ? 'U' : '' )
-                    . ( $_->is_primary_key || $_->is_foreign_key || $_->is_unique ? ']' : '' )
-                : '' )
-                . '\ '
-            } @fields
-        ) . '\l';
-        my $label = $show_fields ? "{$table_name|$field_str}" : $table_name;
-#        $gv->add_node( $table_name, label => $label );
-        $gv->add_node( $table_name, label => $label, ($node_shape eq 'record' ? ( shape => $node_shape ) : ()) );
+        my $field_str = '';
+        if ($show_fields) {
+
+          my @fmt_fields;
+          foreach my $field (@fields) {
+
+            my $field_type;
+            if ($show_datatypes) {
+
+              $field_type = $field->data_type;
+
+              # For the integer type, transform into different types based on
+              # requested size, if a size is given.
+              if ($field->size and $friendly_ints and (lc $field_type) eq 'integer') {
+                # Automatically translate to int2, int4, int8
+                # Type (Bits)     Max. Signed/Unsigned    Length
+                # tinyint* (8)    128                     3
+                #                 255                     3
+                # smallint (16)   32767                   5
+                #                 65535                   5
+                # mediumint* (24) 8388607                 7
+                #                 16777215                8
+                # int (32)        2147483647              10
+                #                 4294967295              11
+                # bigint (64)     9223372036854775807     19
+                #                 18446744073709551615    20
+                #
+                # * tinyint and mediumint are nonstandard extensions which are
+                #   only available under MySQL (to my knowledge)
+                my $size = $field->size;
+                if ($size <= 3 and $friendly_ints_ex) {
+                  $field_type = 'tinyint',
+                }
+                elsif ($size <= 5) {
+                  $field_type = 'smallint';
+                }
+                elsif ($size <= 8 and $friendly_ints_ex) {
+                  $field_type = 'mediumint';
+                }
+                elsif ($size <= 11) {
+                  $field_type = 'integer';
+                }
+                else {
+                  $field_type = 'bigint';
+                }
+              }
+
+              if (
+                $show_sizes
+                  and
+                $field->size
+                  and
+                ($field_type =~ /^(var)?char2?$/ or $field_type eq 'numeric' or $field_type eq 'decimal')
+              ) {
+                $field_type .= '(' . $field->size . ')';
+              }
+            }
+
+            my $constraints;
+            if ($show_constraints) {
+              my @constraints;
+              push(@constraints, 'PK') if $field->is_primary_key;
+              push(@constraints, 'FK') if $field->is_foreign_key;
+              push(@constraints, 'U')  if $field->is_unique;
+
+              $constraints = join (',', @constraints);
+            }
+
+            # construct the field line from all info gathered so far
+            push @fmt_fields, join (' ',
+              '-',
+              $field->name,
+              $field_type || (),
+              $constraints ? "[$constraints]" : (),
+            );
+
+          }
+
+          # join field lines with graphviz formatting
+          $field_str = join ('\l', @fmt_fields) . '\l';
+        }
+
+        my $index_str = '';
+        if ($show_indexes) {
+
+          my @fmt_indexes;
+          foreach my $index ($table->get_indices) {
+            next unless $index->is_valid;
+
+            push @fmt_indexes, join (' ',
+              '*',
+              $show_index_names ? $index->name . ':' : (),
+              join (', ', $index->fields),
+              ($index->type eq 'UNIQUE') ? '[U]' : (),
+            );
+          }
+
+          # join index lines with graphviz formatting (if any indexes at all)
+          $index_str = join ('\l', @fmt_indexes) . '\l' if @fmt_indexes;
+        }
+
+        my $table_name = $table->name;
+        my $name_str = $table_name . '\n';
+
+        # escape spaces
+        for ($name_str, $field_str, $index_str) {
+          $_ =~ s/ /\\ /g;
+        }
+
+
+        # only the 'record' type supports nice formatting
+        if ($node_shape eq 'record') {
+
+            # the necessity to supply shape => 'record' is a graphviz bug 
+            $gv->add_node( $table_name,
+              shape => 'record',
+              label => sprintf ('{%s}',
+                join ('|',
+                  $name_str,
+                  $field_str || (),
+                  $index_str || (),
+                ),
+              ),
+            );
+        }
+        else {
+            my $sep = sprintf ('%s\n',
+                '-' x ( (length $table_name) + 2)
+            );
+
+            $gv->add_node( $table_name,
+                label => join ($sep,
+                    $name_str,
+                    $field_str || (),
+                    $index_str || (),
+                ),
+            );
+        }
+
+
         debug("Processing table '$table_name'");
 
         debug("Fields = ", join(', ', map { $_->name } @fields));
@@ -422,8 +552,8 @@ sub produce {
         for my $i ( 0 .. $#tables ) {
             my $table1 = $tables[ $i ];
             for my $j ( 0 .. $#tables ) {
+                next if $i == $j;
                 my $table2 = $tables[ $j ];
-                next if $table1 eq $table2;
                 next if $done{ $table1 }{ $table2 };
                 $gv->add_edge( $table2, $table1 );
                 $done{ $table1 }{ $table2 } = 1;
@@ -435,15 +565,19 @@ sub produce {
     #
     # Print the image.
     #
-    my $output_method = "as_$output_type";
     if ( $out_file ) {
+      if (openhandle ($out_file)) {
+        print $out_file $gv->$output_method;
+      }
+      else {
         open my $fh, ">$out_file" or die "Can't write '$out_file': $!\n";
         binmode $fh;
         print $fh $gv->$output_method;
         close $fh;
+      }
     }
     else {
-        return $gv->$output_method;
+      return $gv->$output_method;
     }
 }
 
@@ -457,6 +591,10 @@ sub produce {
 
 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
 
+=head2 CONTRIBUTORS
+
+Jonathan Yu E<lt>frequency@cpan.orgE<gt>
+
 =head1 SEE ALSO
 
 SQL::Translator, GraphViz