package SQL::Translator::Producer::GraphViz;
# -------------------------------------------------------------------
-# $Id: GraphViz.pm,v 1.12 2004-02-20 02:41:47 dlc Exp $
+# $Id$
# -------------------------------------------------------------------
# Copyright (C) 2002-4 SQLFairy Authors
#
add_color => 1,
show_constraints => 1,
show_datatypes => 1,
- show_col_sizes => 1
+ show_sizes => 1
}
) or die SQL::Translator->error;
height (in inches) of the output grahic
+=item * fontsize
+
+custom font size for node and edge labels (note that arbitrarily large
+sizes may be ignored due to page size or graph size constraints)
+
+=item * fontname
+
+custom font name (or full path to font file) for node, edge, and graph
+labels
+
+=item * nodeattrs
+
+reference to a hash of node attribute names and their values; these
+may override general fontname or fontsize parameter
+
+=item * edgeattrs
+
+reference to a hash of edge attribute names and their values; these
+may override general fontname or fontsize parameter
+
+=item * graphattrs
+
+reference to a hash of graph attribute names and their values; these
+may override the general fontname parameter
+
=item * show_fields (DEFAULT: true)
if set to a true value, the names of the colums in a table will
displayed next to each column's name; this option will have no
effect if the value of show_fields is set to false
-=item * show_col_sizes
+=item * show_sizes
if set to a true value, the size (in bytes) of each CHAR and
VARCHAR column will be displayed in parentheses next to the
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_name
+
+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_name 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 SQL::Translator::Utils qw(debug);
use vars qw[ $VERSION $DEBUG ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.12 $ =~ /(\d+)\.(\d+)/;
+$VERSION = sprintf "%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/;
$DEBUG = 0 unless defined $DEBUG;
use constant VALID_LAYOUT => {
? $args->{'width'} : 8.5;
my $height = defined $args->{'height'}
? $args->{'height'} : 11;
+ my $fontsize = $args->{'fontsize'};
+ my $fontname = $args->{'fontname'};
+ my $edgeattrs = $args->{'edgeattrs'} || {};
+ my $graphattrs = $args->{'graphattrs'} || {};
+ my $nodeattrs = $args->{'nodeattrs'} || {};
my $show_fields = defined $args->{'show_fields'}
? $args->{'show_fields'} : 1;
my $add_color = $args->{'add_color'};
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_name = $args->{'show_index_name'} || 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'};
- my %skip = map { s/^\s+|\s+$//g; $_, 1 }
+ my $skip_fields = $args->{'skip_fields'} || '';
+ my %skip = map { s/^\s+|\s+$//g; length $_ ? ($_, 1) : () }
split ( /,/, $skip_fields );
$natural_join ||= $join_pk_only;
node => {
shape => $node_shape,
style => 'filled',
- fillcolor => 'white'
- }
+ fillcolor => 'white',
+ },
);
$args{'width'} = $width if $width;
$args{'height'} = $height if $height;
+ # set fontsize for edge and node labels if specified
+ if ($fontsize) {
+ $args{'node'}->{'fontsize'} = $fontsize;
+ $args{'edge'} = {} unless $args{'edge'};
+ $args{'edge'}->{'fontsize'} = $fontsize;
+ }
+ # set the font name globally for node, edge, and graph labels if
+ # specified (use node, edge, or graph attributes for individual
+ # font specification)
+ if ($fontname) {
+ $args{'node'}->{'fontname'} = $fontname;
+ $args{'edge'} = {} unless $args{'edge'};
+ $args{'edge'}->{'fontname'} = $fontname;
+ $args{'graph'} = {} unless $args{'graph'};
+ $args{'graph'}->{'fontname'} = $fontname;
+ }
+ # set additional node, edge, and graph attributes; these may
+ # possibly override ones set before
+ while (my ($key,$val) = each %$nodeattrs) {
+ $args{'node'}->{$key} = $val;
+ }
+ $args{'edge'} = {} if %$edgeattrs && !$args{'edge'};
+ while (my ($key,$val) = each %$edgeattrs) {
+ $args{'edge'}->{$key} = $val;
+ }
+ $args{'graph'} = {} if %$edgeattrs && !$args{'graph'};
+ while (my ($key,$val) = each %$graphattrs) {
+ $args{'graph'}->{$key} = $val;
+ }
my $gv = GraphViz->new( %args ) or die "Can't create GraphViz object\n";
@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 );
+ my $label = '{' . $table_name;
+ if ($show_fields) {
+ my $field_str = '';
+ foreach my $field (@fields) {
+ $field_str .= '-\ ' . $field->name;
+ if ($show_datatypes) {
+ my $dt = lc($field->data_type);
+
+ # For the integer type, transform into different types based on
+ # requested size, if a size is given.
+ if ($friendly_ints && $dt eq 'integer' && $field->size) {
+ # 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 10
+ # 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 > 10) {
+ $dt = 'bigint';
+ }
+ elsif ($size > 5) {
+ $dt = 'integer';
+ if ($friendly_ints_ex && $size <= 8) {
+ $dt = 'mediumint';
+ }
+ }
+ else {
+ $dt = 'smallint';
+ if ($friendly_ints_ex && $size <= 3) {
+ $dt = 'tinyint';
+ }
+ }
+ }
+
+ $field_str .= '\ ' . $dt;
+ if ($show_sizes && $field->size && ($dt =~ /^(var)?char2?$/ || $dt eq 'numeric' || $dt eq 'decimal')) {
+ $field_str .= '(' . $field->size . ')';
+ }
+ }
+ 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;
+ if (scalar(@constraints)) {
+ $field_str .= '\ [' . join(',\ ', @constraints) . ']';
+ }
+ }
+ $field_str .= '\l';
+ }
+ $label .= '|' . $field_str;
+ }
+
+ if ($show_indexes) {
+ my $index_str = '';
+ foreach my $index ($table->get_indices) {
+ next unless $index->is_valid;
+
+ $index_str .= '*\ ';
+ if ($show_index_name) {
+ $index_str .= $index->name . ': ';
+ }
+ $index_str .= join(', ', $index->fields);
+ if ($index->type eq 'UNIQUE') {
+ $index_str .= '\ [U]';
+ }
+ $index_str .= '\l';
+ }
+ # Only add the last box if index_str is non-null
+ if (length $index_str) {
+ $label .= '|' . $index_str;
+ }
+ }
+ $label .= '}';
+# $gv->add_node( $table_name, label => $label );
+# $gv->add_node( $table_name, label => $label, ($node_shape eq 'record' ? ( shape => $node_shape ) : ()) );
+ $gv->add_node( $table_name, label => $label, shape => $node_shape );
debug("Processing table '$table_name'");
debug("Fields = ", join(', ', map { $_->name } @fields));
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;
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