package SQL::Translator::Producer::GraphViz;
# -------------------------------------------------------------------
-# $Id: GraphViz.pm,v 1.14 2007-09-26 13:20:09 schiffbruechige Exp $
-# -------------------------------------------------------------------
-# 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
=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')
which columns are included in the index. this option requires
that show_fields is a true value as well
-=item * show_index_name
+=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_name is false, then a list of indexed columns
+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 tinyint, smallint, integer or bigint depending on the field's
+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.59';
$DEBUG = 0 unless defined $DEBUG;
use constant VALID_LAYOUT => {
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;
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 $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'} || '';
) 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 };
$_ = 0 if $_ < 0;
}
- #
- # Create GraphViz and see if we can produce the output type.
- #
my %args = (
directed => $natural_join ? 0 : 1,
layout => $layout,
$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 $label = '{' . $table_name;
+ my $field_str = '';
if ($show_fields) {
- my $field_str = '';
+
+ my @fmt_fields;
foreach my $field (@fields) {
- $field_str .= '-\ ' . $field->name;
+
+ my $field_type;
if ($show_datatypes) {
- my $dt = lc($field->data_type);
+
+ $field_type = $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) {
+ if ($field->size and $friendly_ints and (lc $field_type) eq 'integer') {
# Automatically translate to int2, int4, int8
- # Type (Bits) Max. Signed Length
- # tinyint (8) 128 3
- # smallint (16) 32767 5
- # int (32) 2147483647 10
- # bigint (64) 9223372036854775807 19
- if ($field->size > 10) {
- $dt = 'bigint';
+ # 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 ($field->size > 5) {
- $dt = 'integer';
+ elsif ($size <= 5) {
+ $field_type = 'smallint';
}
- elsif ($field->size > 3) {
- $dt = 'smallint';
+ elsif ($size <= 8 and $friendly_ints_ex) {
+ $field_type = 'mediumint';
}
- else { # 8 bits
- $dt = 'tinyint';
+ elsif ($size <= 11) {
+ $field_type = 'integer';
+ }
+ else {
+ $field_type = 'bigint';
}
}
- $field_str .= '\ ' . $dt;
- if ($show_sizes && $field->size && ($dt =~ /^(var)?char2?$/ || $dt eq 'numeric' || $dt eq 'decimal')) {
- $field_str .= '(' . $field->size . ')';
+ 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;
- if (scalar(@constraints)) {
- $field_str .= '\ [' . join(',\ ', @constraints) . ']';
- }
+
+ $constraints = join (',', @constraints);
}
- $field_str .= '\l';
+
+ # construct the field line from all info gathered so far
+ push @fmt_fields, join (' ',
+ '-',
+ $field->name,
+ $field_type || (),
+ $constraints ? "[$constraints]" : (),
+ );
+
}
- $label .= '|' . $field_str;
+
+ # join field lines with graphviz formatting
+ $field_str = join ('\l', @fmt_fields) . '\l';
}
+ my $index_str = '';
if ($show_indexes) {
- my $index_str = '';
+
+ my @fmt_indexes;
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;
+ 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;
}
- $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 );
+
+ 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));
#
# 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;
}
}
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