Better debug output
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Schema.pm
CommitLineData
3c5de62a 1package SQL::Translator::Schema;
2
3# ----------------------------------------------------------------------
478f608d 4# Copyright (C) 2002-2009 SQLFairy Authors
3c5de62a 5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18# 02111-1307 USA
19# -------------------------------------------------------------------
20
21=pod
22
23=head1 NAME
24
25SQL::Translator::Schema - SQL::Translator schema object
26
27=head1 SYNOPSIS
28
29 use SQL::Translator::Schema;
97382c6d 30 my $schema = SQL::Translator::Schema->new(
31 name => 'Foo',
32 database => 'MySQL',
33 );
34 my $table = $schema->add_table( name => 'foo' );
35 my $view = $schema->add_view( name => 'bar', sql => '...' );
36
3c5de62a 37
38=head1 DESCSIPTION
39
40C<SQL::Translator::Schema> is the object that accepts, validates, and
41returns the database structure.
42
43=head1 METHODS
44
45=cut
46
47use strict;
9480e70b 48use SQL::Translator::Schema::Constants;
daf24e05 49use SQL::Translator::Schema::Procedure;
3c5de62a 50use SQL::Translator::Schema::Table;
5974bee7 51use SQL::Translator::Schema::Trigger;
3c5de62a 52use SQL::Translator::Schema::View;
4c4636ed 53
5974bee7 54use SQL::Translator::Utils 'parse_list_arg';
3c5de62a 55
b6a880d1 56use base 'SQL::Translator::Schema::Object';
da06ac74 57use vars qw[ $VERSION $TABLE_ORDER $VIEW_ORDER $TRIGGER_ORDER $PROC_ORDER ];
58
4ab3763d 59$VERSION = '1.59';
9371be50 60
da8ab524 61__PACKAGE__->_attributes(qw/name database translator/);
3c5de62a 62
97382c6d 63# ----------------------------------------------------------------------
64sub as_graph {
65
3c5de62a 66=pod
67
97382c6d 68=head2 as_graph
3c5de62a 69
97382c6d 70Returns the schema as an L<SQL::Translator::Schema::Graph> object.
3c5de62a 71
72=cut
4c4636ed 73 require SQL::Translator::Schema::Graph;
3c5de62a 74
da8ab524 75 my $self = shift;
4c4636ed 76
97382c6d 77 return SQL::Translator::Schema::Graph->new(
da8ab524 78 translator => $self->translator );
10f36920 79}
80
3c5de62a 81# ----------------------------------------------------------------------
e7ca845a 82sub as_graph_pm {
83
84=pod
85
da5a1bae 86=head2 as_graph_pm
e7ca845a 87
88Returns a Graph::Directed object with the table names for nodes.
89
90=cut
91
da5a1bae 92 require Graph::Directed;
93
e7ca845a 94 my $self = shift;
95 my $g = Graph::Directed->new;
96
97 for my $table ( $self->get_tables ) {
98 my $tname = $table->name;
99 $g->add_vertex( $tname );
100
101 for my $field ( $table->get_fields ) {
102 if ( $field->is_foreign_key ) {
103 my $fktable = $field->foreign_key_reference->reference_table;
104
105 $g->add_edge( $fktable, $tname );
106 }
107 }
108 }
109
110 return $g;
111}
112
113# ----------------------------------------------------------------------
76dce619 114sub add_table {
3c5de62a 115
116=pod
117
76dce619 118=head2 add_table
3c5de62a 119
76dce619 120Add a table object. Returns the new SQL::Translator::Schema::Table object.
99248301 121The "name" parameter is required. If you try to create a table with the
122same name as an existing table, you will get an error and the table will
123not be created.
3c5de62a 124
68e8e2e1 125 my $t1 = $schema->add_table( name => 'foo' ) or die $schema->error;
126 my $t2 = SQL::Translator::Schema::Table->new( name => 'bar' );
127 $t2 = $schema->add_table( $table_bar ) or die $schema->error;
3c5de62a 128
129=cut
130
99248301 131 my $self = shift;
132 my $table_class = 'SQL::Translator::Schema::Table';
133 my $table;
134
135 if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
136 $table = shift;
da8ab524 137 $table->schema($self);
99248301 138 }
139 else {
140 my %args = @_;
141 $args{'schema'} = $self;
da8ab524 142 $table = $table_class->new( \%args )
143 or return $self->error( $table_class->error );
99248301 144 }
3c5de62a 145
d0b43695 146 $table->order( ++$TABLE_ORDER );
da8ab524 147
23044758 148 # We know we have a name as the Table->new above errors if none given.
149 my $table_name = $table->name;
99248301 150
da8ab524 151 if ( defined $self->{'tables'}{$table_name} ) {
99248301 152 return $self->error(qq[Can't create table: "$table_name" exists]);
153 }
154 else {
da8ab524 155 $self->{'tables'}{$table_name} = $table;
99248301 156 }
3c5de62a 157
158 return $table;
159}
160
161# ----------------------------------------------------------------------
650f87eb 162sub drop_table {
163
164=pod
165
166=head2 drop_table
167
168Remove a table from the schema. Returns the table object if the table was found
169and removed, an error otherwise. The single parameter can be either a table
170name or an C<SQL::Translator::Schema::Table> object. The "cascade" parameter
171can be set to 1 to also drop all triggers on the table, default is 0.
172
173 $schema->drop_table('mytable');
174 $schema->drop_table('mytable', cascade => 1);
175
176=cut
177
da8ab524 178 my $self = shift;
179 my $table_class = 'SQL::Translator::Schema::Table';
650f87eb 180 my $table_name;
181
182 if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
183 $table_name = shift->name;
184 }
185 else {
186 $table_name = shift;
187 }
da8ab524 188 my %args = @_;
650f87eb 189 my $cascade = $args{'cascade'};
190
da8ab524 191 if ( !exists $self->{'tables'}{$table_name} ) {
650f87eb 192 return $self->error(qq[Can't drop table: $table_name" doesn't exist]);
193 }
194
da8ab524 195 my $table = delete $self->{'tables'}{$table_name};
196
197 if ($cascade) {
650f87eb 198
650f87eb 199 # Drop all triggers on this table
da8ab524 200 $self->drop_trigger()
201 for ( grep { $_->on_table eq $table_name } @{ $self->{'triggers'} } );
650f87eb 202 }
203 return $table;
204}
205
206# ----------------------------------------------------------------------
daf24e05 207sub add_procedure {
208
209=pod
210
211=head2 add_procedure
212
650f87eb 213Add a procedure object. Returns the new SQL::Translator::Schema::Procedure
214object. The "name" parameter is required. If you try to create a procedure
215with the same name as an existing procedure, you will get an error and the
216procedure will not be created.
daf24e05 217
218 my $p1 = $schema->add_procedure( name => 'foo' );
219 my $p2 = SQL::Translator::Schema::Procedure->new( name => 'bar' );
220 $p2 = $schema->add_procedure( $procedure_bar ) or die $schema->error;
221
222=cut
223
224 my $self = shift;
225 my $procedure_class = 'SQL::Translator::Schema::Procedure';
226 my $procedure;
227
228 if ( UNIVERSAL::isa( $_[0], $procedure_class ) ) {
229 $procedure = shift;
da8ab524 230 $procedure->schema($self);
daf24e05 231 }
232 else {
233 my %args = @_;
234 $args{'schema'} = $self;
235 return $self->error('No procedure name') unless $args{'name'};
da8ab524 236 $procedure = $procedure_class->new( \%args )
237 or return $self->error( $procedure_class->error );
daf24e05 238 }
239
240 $procedure->order( ++$PROC_ORDER );
da8ab524 241 my $procedure_name = $procedure->name
242 or return $self->error('No procedure name');
daf24e05 243
da8ab524 244 if ( defined $self->{'procedures'}{$procedure_name} ) {
daf24e05 245 return $self->error(
da8ab524 246 qq[Can't create procedure: "$procedure_name" exists] );
daf24e05 247 }
248 else {
da8ab524 249 $self->{'procedures'}{$procedure_name} = $procedure;
daf24e05 250 }
251
252 return $procedure;
253}
da8ab524 254
650f87eb 255# ----------------------------------------------------------------------
256sub drop_procedure {
257
258=pod
259
260=head2 drop_procedure
261
262Remove a procedure from the schema. Returns the procedure object if the
263procedure was found and removed, an error otherwise. The single parameter
264can be either a procedure name or an C<SQL::Translator::Schema::Procedure>
265object.
266
267 $schema->drop_procedure('myprocedure');
268
269=cut
270
da8ab524 271 my $self = shift;
272 my $proc_class = 'SQL::Translator::Schema::Procedure';
650f87eb 273 my $proc_name;
274
275 if ( UNIVERSAL::isa( $_[0], $proc_class ) ) {
276 $proc_name = shift->name;
277 }
278 else {
279 $proc_name = shift;
280 }
281
da8ab524 282 if ( !exists $self->{'procedures'}{$proc_name} ) {
283 return $self->error(
284 qq[Can't drop procedure: $proc_name" doesn't exist]);
650f87eb 285 }
286
da8ab524 287 my $proc = delete $self->{'procedures'}{$proc_name};
650f87eb 288
289 return $proc;
290}
daf24e05 291
292# ----------------------------------------------------------------------
5974bee7 293sub add_trigger {
294
295=pod
296
297=head2 add_trigger
298
299Add a trigger object. Returns the new SQL::Translator::Schema::Trigger object.
300The "name" parameter is required. If you try to create a trigger with the
301same name as an existing trigger, you will get an error and the trigger will
302not be created.
303
304 my $t1 = $schema->add_trigger( name => 'foo' );
305 my $t2 = SQL::Translator::Schema::Trigger->new( name => 'bar' );
306 $t2 = $schema->add_trigger( $trigger_bar ) or die $schema->error;
307
308=cut
309
310 my $self = shift;
311 my $trigger_class = 'SQL::Translator::Schema::Trigger';
312 my $trigger;
313
314 if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
315 $trigger = shift;
da8ab524 316 $trigger->schema($self);
5974bee7 317 }
318 else {
319 my %args = @_;
daf24e05 320 $args{'schema'} = $self;
5974bee7 321 return $self->error('No trigger name') unless $args{'name'};
da8ab524 322 $trigger = $trigger_class->new( \%args )
323 or return $self->error( $trigger_class->error );
5974bee7 324 }
325
326 $trigger->order( ++$TRIGGER_ORDER );
5974bee7 327
4faaaac6 328 my $trigger_name = $trigger->name or return $self->error('No trigger name');
da8ab524 329 if ( defined $self->{'triggers'}{$trigger_name} ) {
5974bee7 330 return $self->error(qq[Can't create trigger: "$trigger_name" exists]);
331 }
332 else {
da8ab524 333 $self->{'triggers'}{$trigger_name} = $trigger;
5974bee7 334 }
335
336 return $trigger;
337}
da8ab524 338
650f87eb 339# ----------------------------------------------------------------------
340sub drop_trigger {
341
342=pod
343
344=head2 drop_trigger
345
346Remove a trigger from the schema. Returns the trigger object if the trigger was
347found and removed, an error otherwise. The single parameter can be either a
348trigger name or an C<SQL::Translator::Schema::Trigger> object.
349
350 $schema->drop_trigger('mytrigger');
351
352=cut
353
da8ab524 354 my $self = shift;
355 my $trigger_class = 'SQL::Translator::Schema::Trigger';
650f87eb 356 my $trigger_name;
357
358 if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
359 $trigger_name = shift->name;
360 }
361 else {
362 $trigger_name = shift;
363 }
364
da8ab524 365 if ( !exists $self->{'triggers'}{$trigger_name} ) {
366 return $self->error(
367 qq[Can't drop trigger: $trigger_name" doesn't exist]);
650f87eb 368 }
369
da8ab524 370 my $trigger = delete $self->{'triggers'}{$trigger_name};
650f87eb 371
372 return $trigger;
373}
5974bee7 374
375# ----------------------------------------------------------------------
76dce619 376sub add_view {
3c5de62a 377
378=pod
379
76dce619 380=head2 add_view
3c5de62a 381
76dce619 382Add a view object. Returns the new SQL::Translator::Schema::View object.
99248301 383The "name" parameter is required. If you try to create a view with the
384same name as an existing view, you will get an error and the view will
385not be created.
386
68e8e2e1 387 my $v1 = $schema->add_view( name => 'foo' );
388 my $v2 = SQL::Translator::Schema::View->new( name => 'bar' );
389 $v2 = $schema->add_view( $view_bar ) or die $schema->error;
3c5de62a 390
391=cut
392
da8ab524 393 my $self = shift;
99248301 394 my $view_class = 'SQL::Translator::Schema::View';
395 my $view;
396
397 if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
398 $view = shift;
da8ab524 399 $view->schema($self);
99248301 400 }
401 else {
402 my %args = @_;
daf24e05 403 $args{'schema'} = $self;
99248301 404 return $self->error('No view name') unless $args{'name'};
405 $view = $view_class->new( \%args ) or return $view_class->error;
406 }
3c5de62a 407
d0b43695 408 $view->order( ++$VIEW_ORDER );
99248301 409 my $view_name = $view->name or return $self->error('No view name');
410
da8ab524 411 if ( defined $self->{'views'}{$view_name} ) {
99248301 412 return $self->error(qq[Can't create view: "$view_name" exists]);
413 }
414 else {
da8ab524 415 $self->{'views'}{$view_name} = $view;
99248301 416 }
3c5de62a 417
76dce619 418 return $view;
3c5de62a 419}
420
421# ----------------------------------------------------------------------
650f87eb 422sub drop_view {
423
424=pod
425
426=head2 drop_view
427
428Remove a view from the schema. Returns the view object if the view was found
429and removed, an error otherwise. The single parameter can be either a view
430name or an C<SQL::Translator::Schema::View> object.
431
432 $schema->drop_view('myview');
433
434=cut
435
da8ab524 436 my $self = shift;
437 my $view_class = 'SQL::Translator::Schema::View';
650f87eb 438 my $view_name;
439
440 if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
441 $view_name = shift->name;
442 }
443 else {
444 $view_name = shift;
445 }
446
da8ab524 447 if ( !exists $self->{'views'}{$view_name} ) {
650f87eb 448 return $self->error(qq[Can't drop view: $view_name" doesn't exist]);
449 }
450
da8ab524 451 my $view = delete $self->{'views'}{$view_name};
650f87eb 452
453 return $view;
454}
455
456# ----------------------------------------------------------------------
99248301 457sub database {
458
459=pod
460
461=head2 database
462
463Get or set the schema's database. (optional)
464
465 my $database = $schema->database('PostgreSQL');
466
467=cut
468
469 my $self = shift;
470 $self->{'database'} = shift if @_;
471 return $self->{'database'} || '';
472}
473
474# ----------------------------------------------------------------------
76dce619 475sub is_valid {
3c5de62a 476
477=pod
478
76dce619 479=head2 is_valid
3c5de62a 480
76dce619 481Returns true if all the tables and views are valid.
3c5de62a 482
76dce619 483 my $ok = $schema->is_valid or die $schema->error;
484
485=cut
486
487 my $self = shift;
488
489 return $self->error('No tables') unless $self->get_tables;
490
491 for my $object ( $self->get_tables, $self->get_views ) {
492 return $object->error unless $object->is_valid;
493 }
494
495 return 1;
496}
497
498# ----------------------------------------------------------------------
daf24e05 499sub get_procedure {
500
501=pod
502
503=head2 get_procedure
504
505Returns a procedure by the name provided.
506
507 my $procedure = $schema->get_procedure('foo');
508
509=cut
510
da8ab524 511 my $self = shift;
daf24e05 512 my $procedure_name = shift or return $self->error('No procedure name');
da8ab524 513 return $self->error(qq[Table "$procedure_name" does not exist])
514 unless exists $self->{'procedures'}{$procedure_name};
515 return $self->{'procedures'}{$procedure_name};
daf24e05 516}
517
518# ----------------------------------------------------------------------
519sub get_procedures {
520
521=pod
522
523=head2 get_procedures
524
525Returns all the procedures as an array or array reference.
526
527 my @procedures = $schema->get_procedures;
528
529=cut
530
da8ab524 531 my $self = shift;
532 my @procedures =
533 map { $_->[1] }
534 sort { $a->[0] <=> $b->[0] }
535 map { [ $_->order, $_ ] } values %{ $self->{'procedures'} };
daf24e05 536
da8ab524 537 if (@procedures) {
daf24e05 538 return wantarray ? @procedures : \@procedures;
539 }
540 else {
541 $self->error('No procedures');
542 return wantarray ? () : undef;
543 }
544}
545
546# ----------------------------------------------------------------------
76dce619 547sub get_table {
548
549=pod
550
551=head2 get_table
552
553Returns a table by the name provided.
554
555 my $table = $schema->get_table('foo');
556
557=cut
558
da8ab524 559 my $self = shift;
76dce619 560 my $table_name = shift or return $self->error('No table name');
6a25a87d 561 my $case_insensitive = shift;
562 if ( $case_insensitive ) {
563 $table_name = uc($table_name);
564 foreach my $table ( keys %{$self->{tables}} ) {
565 return $self->{tables}{$table} if $table_name eq uc($table);
566 }
567 return $self->error(qq[Table "$table_name" does not exist]);
568 }
da8ab524 569 return $self->error(qq[Table "$table_name" does not exist])
570 unless exists $self->{'tables'}{$table_name};
571 return $self->{'tables'}{$table_name};
76dce619 572}
573
574# ----------------------------------------------------------------------
575sub get_tables {
576
577=pod
578
579=head2 get_tables
580
581Returns all the tables as an array or array reference.
582
583 my @tables = $schema->get_tables;
584
585=cut
586
587 my $self = shift;
da8ab524 588 my @tables =
589 map { $_->[1] }
590 sort { $a->[0] <=> $b->[0] }
591 map { [ $_->order, $_ ] } values %{ $self->{'tables'} };
76dce619 592
da8ab524 593 if (@tables) {
76dce619 594 return wantarray ? @tables : \@tables;
595 }
596 else {
597 $self->error('No tables');
598 return wantarray ? () : undef;
599 }
600}
601
602# ----------------------------------------------------------------------
daf24e05 603sub get_trigger {
604
605=pod
606
607=head2 get_trigger
608
609Returns a trigger by the name provided.
610
611 my $trigger = $schema->get_trigger('foo');
612
613=cut
614
da8ab524 615 my $self = shift;
daf24e05 616 my $trigger_name = shift or return $self->error('No trigger name');
da8ab524 617 return $self->error(qq[Table "$trigger_name" does not exist])
618 unless exists $self->{'triggers'}{$trigger_name};
619 return $self->{'triggers'}{$trigger_name};
daf24e05 620}
621
622# ----------------------------------------------------------------------
623sub get_triggers {
624
625=pod
626
627=head2 get_triggers
628
629Returns all the triggers as an array or array reference.
630
631 my @triggers = $schema->get_triggers;
632
633=cut
634
da8ab524 635 my $self = shift;
636 my @triggers =
637 map { $_->[1] }
638 sort { $a->[0] <=> $b->[0] }
639 map { [ $_->order, $_ ] } values %{ $self->{'triggers'} };
daf24e05 640
da8ab524 641 if (@triggers) {
daf24e05 642 return wantarray ? @triggers : \@triggers;
643 }
644 else {
645 $self->error('No triggers');
646 return wantarray ? () : undef;
647 }
648}
649
650# ----------------------------------------------------------------------
76dce619 651sub get_view {
652
653=pod
654
655=head2 get_view
656
657Returns a view by the name provided.
658
659 my $view = $schema->get_view('foo');
3c5de62a 660
661=cut
662
da8ab524 663 my $self = shift;
76dce619 664 my $view_name = shift or return $self->error('No view name');
da8ab524 665 return $self->error('View "$view_name" does not exist')
666 unless exists $self->{'views'}{$view_name};
667 return $self->{'views'}{$view_name};
76dce619 668}
3c5de62a 669
76dce619 670# ----------------------------------------------------------------------
671sub get_views {
3c5de62a 672
76dce619 673=pod
674
675=head2 get_views
676
677Returns all the views as an array or array reference.
678
679 my @views = $schema->get_views;
680
681=cut
682
683 my $self = shift;
da8ab524 684 my @views =
685 map { $_->[1] }
686 sort { $a->[0] <=> $b->[0] }
687 map { [ $_->order, $_ ] } values %{ $self->{'views'} };
76dce619 688
da8ab524 689 if (@views) {
76dce619 690 return wantarray ? @views : \@views;
691 }
692 else {
693 $self->error('No views');
694 return wantarray ? () : undef;
695 }
3c5de62a 696}
697
99248301 698# ----------------------------------------------------------------------
9480e70b 699sub make_natural_joins {
700
701=pod
702
703=head2 make_natural_joins
704
705Creates foriegn key relationships among like-named fields in different
706tables. Accepts the following arguments:
707
708=over 4
709
650f87eb 710=item * join_pk_only
9480e70b 711
712A True or False argument which determins whether or not to perform
713the joins from primary keys to fields of the same name in other tables
714
715=item * skip_fields
716
717A list of fields to skip in the joins
718
719=back 4
720
721 $schema->make_natural_joins(
722 join_pk_only => 1,
723 skip_fields => 'name,department_id',
724 );
725
726=cut
727
728 my $self = shift;
729 my %args = @_;
730 my $join_pk_only = $args{'join_pk_only'} || 0;
da8ab524 731 my %skip_fields =
732 map { s/^\s+|\s+$//g; $_, 1 } @{ parse_list_arg( $args{'skip_fields'} ) };
9480e70b 733
734 my ( %common_keys, %pk );
735 for my $table ( $self->get_tables ) {
736 for my $field ( $table->get_fields ) {
737 my $field_name = $field->name or next;
da8ab524 738 next if $skip_fields{$field_name};
739 $pk{$field_name} = 1 if $field->is_primary_key;
740 push @{ $common_keys{$field_name} }, $table->name;
9480e70b 741 }
da8ab524 742 }
743
9480e70b 744 for my $field ( keys %common_keys ) {
da8ab524 745 next if $join_pk_only and !defined $pk{$field};
9480e70b 746
da8ab524 747 my @table_names = @{ $common_keys{$field} };
9480e70b 748 next unless scalar @table_names > 1;
749
750 for my $i ( 0 .. $#table_names ) {
da8ab524 751 my $table1 = $self->get_table( $table_names[$i] ) or next;
9480e70b 752
753 for my $j ( 1 .. $#table_names ) {
da8ab524 754 my $table2 = $self->get_table( $table_names[$j] ) or next;
9480e70b 755 next if $table1->name eq $table2->name;
756
757 $table1->add_constraint(
758 type => FOREIGN_KEY,
759 fields => $field,
760 reference_table => $table2->name,
761 reference_fields => $field,
762 );
650f87eb 763 }
9480e70b 764 }
650f87eb 765 }
9480e70b 766
767 return 1;
768}
769
770# ----------------------------------------------------------------------
99248301 771sub name {
772
773=pod
774
775=head2 name
776
777Get or set the schema's name. (optional)
778
779 my $schema_name = $schema->name('Foo Database');
780
781=cut
782
783 my $self = shift;
784 $self->{'name'} = shift if @_;
785 return $self->{'name'} || '';
786}
787
97382c6d 788# ----------------------------------------------------------------------
789sub translator {
790
791=pod
792
10f36920 793=head2 translator
47fed978 794
97382c6d 795Get the SQL::Translator instance that instantiated the parser.
47fed978 796
797=cut
798
47fed978 799 my $self = shift;
10f36920 800 $self->{'translator'} = shift if @_;
801 return $self->{'translator'};
47fed978 802}
803
d0b43695 804# ----------------------------------------------------------------------
805sub DESTROY {
806 my $self = shift;
807 undef $_ for values %{ $self->{'tables'} };
da8ab524 808 undef $_ for values %{ $self->{'views'} };
d0b43695 809}
810
3c5de62a 8111;
812
813# ----------------------------------------------------------------------
814
815=pod
816
817=head1 AUTHOR
818
97382c6d 819Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
3c5de62a 820
821=cut
da8ab524 822