1 package SQL::Translator::Producer::GraphViz;
3 # -------------------------------------------------------------------
4 # $Id: GraphViz.pm,v 1.12 2004-02-20 02:41:47 dlc Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2002-4 SQLFairy Authors
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License as
10 # published by the Free Software Foundation; version 2.
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 # -------------------------------------------------------------------
27 SQL::Translator::Producer::GraphViz - GraphViz producer for SQL::Translator
33 my $trans = new SQL::Translator(
34 from => 'MySQL', # or your db of choice
37 out_file => 'schema.png',
39 show_constraints => 1,
43 ) or die SQL::Translator->error;
45 $trans->translate or die $trans->error;
49 Creates a graph of a schema using the amazing graphviz
50 (see http://www.graphviz.org/) application (via
51 the GraphViz module). It's nifty--you should try it!
59 the name of the file where the graphviz graphic is to be written
61 =item * layout (DEFAULT: 'dot')
63 determines which layout algorithm GraphViz will use; possible
64 values are 'dot' (the default GraphViz layout for directed graph
65 layouts), 'neato' (for undirected graph layouts - spring model)
66 or 'twopi' (for undirected graph layouts - circular)
68 =item * node_shape (DEFAULT: 'record')
70 sets the node shape of each table in the graph; this can be
71 one of 'record', 'plaintext', 'ellipse', 'circle', 'egg',
72 'triangle', 'box', 'diamond', 'trapezium', 'parallelogram',
73 'house', 'hexagon', or 'octagon'
75 =item * output_type (DEFAULT: 'png')
77 sets the file type of the output graphic; possible values are
78 'ps', 'hpgl', 'pcl', 'mif', 'pic', 'gd', 'gd2', 'gif', 'jpeg',
79 'png', 'wbmp', 'cmap', 'ismap', 'imap', 'vrml', 'vtx', 'mp',
80 'fig', 'svg', 'canon', 'plain' or 'text' (see GraphViz for
81 details on each of these)
83 =item * width (DEFAULT: 8.5)
85 width (in inches) of the output graphic
87 =item * height (DEFAULT: 11)
89 height (in inches) of the output grahic
91 =item * show_fields (DEFAULT: true)
93 if set to a true value, the names of the colums in a table will
94 be displayed in each table's node
98 if set to a true value, only columns which are foreign keys
99 will be displayed in each table's node
101 =item * show_datatypes
103 if set to a true value, the datatype of each column will be
104 displayed next to each column's name; this option will have no
105 effect if the value of show_fields is set to false
107 =item * show_col_sizes
109 if set to a true value, the size (in bytes) of each CHAR and
110 VARCHAR column will be displayed in parentheses next to the
111 column's name; this option will have no effect if the value of
112 show_fields is set to false
114 =item * show_constraints
116 if set to a true value, a field's constraints (i.e., its
117 primary-key-ness, its foreign-key-ness and/or its uniqueness)
118 will appear as a comma-separated list in brackets next to the
119 field's name; this option will have no effect if the value of
120 show_fields is set to false
124 if set to a true value, the graphic will have a background
125 color of 'lightgoldenrodyellow'; otherwise the background
130 if set to a true value, the make_natural_join method of
131 SQL::Translator::Schema will be called before generating the
132 graph; a true value for join_pk_only (see below) implies a
133 true value for this option
137 the value of this option will be passed as the value of the
138 like-named argument in the make_natural_join method (see
139 natural_join above) of SQL::Translator::Schema, if either the
140 value of this option or the natural_join option is set to true
144 the value of this option will be passed as the value of the
145 like-named argument in the make_natural_join method (see
146 natural_join above) of SQL::Translator::Schema, if either
147 the natural_join or join_pk_only options has a true value
156 use SQL::Translator::Schema::Constants;
157 use SQL::Translator::Utils qw(debug);
159 use vars qw[ $VERSION $DEBUG ];
160 $VERSION = sprintf "%d.%02d", q$Revision: 1.12 $ =~ /(\d+)\.(\d+)/;
161 $DEBUG = 0 unless defined $DEBUG;
163 use constant VALID_LAYOUT => {
169 use constant VALID_NODE_SHAPE => {
185 use constant VALID_OUTPUT => {
212 my $schema = $t->schema;
213 my $args = $t->producer_args;
214 local $DEBUG = $t->debug;
216 my $out_file = $args->{'out_file'} || '';
217 my $layout = $args->{'layout'} || 'dot';
218 my $node_shape = $args->{'node_shape'} || 'record';
219 my $output_type = $args->{'output_type'} || 'png';
220 my $width = defined $args->{'width'}
221 ? $args->{'width'} : 8.5;
222 my $height = defined $args->{'height'}
223 ? $args->{'height'} : 11;
224 my $show_fields = defined $args->{'show_fields'}
225 ? $args->{'show_fields'} : 1;
226 my $add_color = $args->{'add_color'};
227 my $natural_join = $args->{'natural_join'};
228 my $show_fk_only = $args->{'show_fk_only'};
229 my $show_datatypes = $args->{'show_datatypes'};
230 my $show_sizes = $args->{'show_sizes'};
231 my $show_constraints = $args->{'show_constraints'};
232 my $join_pk_only = $args->{'join_pk_only'};
233 my $skip_fields = $args->{'skip_fields'};
234 my %skip = map { s/^\s+|\s+$//g; $_, 1 }
235 split ( /,/, $skip_fields );
236 $natural_join ||= $join_pk_only;
238 $schema->make_natural_joins(
239 join_pk_only => $join_pk_only,
240 skip_fields => $args->{'skip_fields'},
243 die "Invalid layout '$layout'" unless VALID_LAYOUT->{ $layout };
244 die "Invalid output type: '$output_type'"
245 unless VALID_OUTPUT->{ $output_type };
246 die "Invalid node shape'$node_shape'"
247 unless VALID_NODE_SHAPE->{ $node_shape };
249 for ( $height, $width ) {
250 $_ = 0 unless $_ =~ /^\d+(.\d)?$/;
255 # Create GraphViz and see if we can produce the output type.
258 directed => $natural_join ? 0 : 1,
261 bgcolor => $add_color ? 'lightgoldenrodyellow' : 'white',
263 shape => $node_shape,
268 $args{'width'} = $width if $width;
269 $args{'height'} = $height if $height;
271 my $gv = GraphViz->new( %args ) or die "Can't create GraphViz object\n";
273 my %nj_registry; # for locations of fields for natural joins
274 my @fk_registry; # for locations of fields for foreign keys
276 for my $table ( $schema->get_tables ) {
277 my $table_name = $table->name;
278 my @fields = $table->get_fields;
279 if ( $show_fk_only ) {
280 @fields = grep { $_->is_foreign_key } @fields;
283 my $field_str = join(
288 . ( $show_datatypes ? '\ ' . $_->data_type : '')
289 . ( $show_sizes && ! $show_datatypes ? '\ ' : '')
290 . ( $show_sizes && $_->data_type =~ /^(VAR)?CHAR2?$/i ? '(' . $_->size . ')' : '')
291 . ( $show_constraints ?
292 ( $_->is_primary_key || $_->is_foreign_key || $_->is_unique ? '\ [' : '' )
293 . ( $_->is_primary_key ? 'PK' : '' )
294 . ( $_->is_primary_key && ($_->is_foreign_key || $_->is_unique) ? ',' : '' )
295 . ( $_->is_foreign_key ? 'FK' : '' )
296 . ( $_->is_unique && ($_->is_primary_key || $_->is_foreign_key) ? ',' : '' )
297 . ( $_->is_unique ? 'U' : '' )
298 . ( $_->is_primary_key || $_->is_foreign_key || $_->is_unique ? ']' : '' )
303 my $label = $show_fields ? "{$table_name|$field_str}" : $table_name;
304 $gv->add_node( $table_name, label => $label );
306 debug("Processing table '$table_name'");
308 debug("Fields = ", join(', ', map { $_->name } @fields));
310 for my $f ( @fields ) {
311 my $name = $f->name or next;
312 my $is_pk = $f->is_primary_key;
313 my $is_unique = $f->is_unique;
316 # Decide if we should skip this field.
318 if ( $natural_join ) {
319 next unless $is_pk || $f->is_foreign_key;
322 my $constraints = $f->{'constraints'};
324 if ( $natural_join && !$skip{ $name } ) {
325 push @{ $nj_registry{ $name } }, $table_name;
329 unless ( $natural_join ) {
330 for my $c ( $table->get_constraints ) {
331 next unless $c->type eq FOREIGN_KEY;
332 my $fk_table = $c->reference_table or next;
334 for my $field_name ( $c->fields ) {
335 for my $fk_field ( $c->reference_fields ) {
336 next unless defined $schema->get_table( $fk_table );
337 push @fk_registry, [ $table_name, $fk_table ];
345 # Make the connections.
348 if ( $natural_join ) {
349 for my $field_name ( keys %nj_registry ) {
350 my @table_names = @{ $nj_registry{ $field_name } || [] } or next;
351 next if scalar @table_names == 1;
352 push @table_bunches, [ @table_names ];
356 @table_bunches = @fk_registry;
360 for my $bunch ( @table_bunches ) {
361 my @tables = @$bunch;
363 for my $i ( 0 .. $#tables ) {
364 my $table1 = $tables[ $i ];
365 for my $j ( 0 .. $#tables ) {
366 my $table2 = $tables[ $j ];
367 next if $table1 eq $table2;
368 next if $done{ $table1 }{ $table2 };
369 $gv->add_edge( $table2, $table1 );
370 $done{ $table1 }{ $table2 } = 1;
371 $done{ $table2 }{ $table1 } = 1;
379 my $output_method = "as_$output_type";
381 open my $fh, ">$out_file" or die "Can't write '$out_file': $!\n";
383 print $fh $gv->$output_method;
387 return $gv->$output_method;
393 # -------------------------------------------------------------------
399 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
403 SQL::Translator, GraphViz