adding COMMENT processing to Pg parser
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / PostgreSQL.pm
1 package SQL::Translator::Parser::PostgreSQL;
2
3 # -------------------------------------------------------------------
4 # $Id: PostgreSQL.pm,v 1.32 2003-11-17 19:09:33 allenday Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7 #                    Allen Day <allenday@users.sourceforge.net>,
8 #                    darren chamberlain <darren@cpan.org>,
9 #                    Chris Mungall <cjm@fruitfly.org>
10 #
11 # This program is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU General Public License as
13 # published by the Free Software Foundation; version 2.
14 #
15 # This program is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 # General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 # 02111-1307  USA
24 # -------------------------------------------------------------------
25
26 =head1 NAME
27
28 SQL::Translator::Parser::PostgreSQL - parser for PostgreSQL
29
30 =head1 SYNOPSIS
31
32   use SQL::Translator;
33   use SQL::Translator::Parser::PostgreSQL;
34
35   my $translator = SQL::Translator->new;
36   $translator->parser("SQL::Translator::Parser::PostgreSQL");
37
38 =head1 DESCRIPTION
39
40 The grammar was started from the MySQL parsers.  Here is the description 
41 from PostgreSQL:
42
43 Table:
44 (http://www.postgresql.org/docs/view.php?version=7.3&idoc=1&file=sql-createtable.html)
45
46   CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
47       { column_name data_type [ DEFAULT default_expr ] 
48          [ column_constraint [, ... ] ]
49       | table_constraint }  [, ... ]
50   )
51   [ INHERITS ( parent_table [, ... ] ) ]
52   [ WITH OIDS | WITHOUT OIDS ]
53   
54   where column_constraint is:
55   
56   [ CONSTRAINT constraint_name ]
57   { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
58     CHECK (expression) |
59     REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
60       [ ON DELETE action ] [ ON UPDATE action ] }
61   [ DEFERRABLE | NOT DEFERRABLE ] 
62   [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
63   
64   and table_constraint is:
65   
66   [ CONSTRAINT constraint_name ]
67   { UNIQUE ( column_name [, ... ] ) |
68     PRIMARY KEY ( column_name [, ... ] ) |
69     CHECK ( expression ) |
70     FOREIGN KEY ( column_name [, ... ] ) 
71      REFERENCES reftable [ ( refcolumn [, ... ] ) ]
72       [ MATCH FULL | MATCH PARTIAL ] 
73       [ ON DELETE action ] [ ON UPDATE action ] }
74   [ DEFERRABLE | NOT DEFERRABLE ] 
75   [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
76
77 Index:
78 (http://www.postgresql.org/docs/view.php?version=7.3&idoc=1&file=sql-createindex.html)
79
80   CREATE [ UNIQUE ] INDEX index_name ON table
81       [ USING acc_method ] ( column [ ops_name ] [, ...] )
82       [ WHERE predicate ]
83   CREATE [ UNIQUE ] INDEX index_name ON table
84       [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
85       [ WHERE predicate ]
86
87 Alter table:
88
89   ALTER TABLE [ ONLY ] table [ * ]
90       ADD [ COLUMN ] column type [ column_constraint [ ... ] ]
91   ALTER TABLE [ ONLY ] table [ * ]
92       ALTER [ COLUMN ] column { SET DEFAULT value | DROP DEFAULT }
93   ALTER TABLE [ ONLY ] table [ * ]
94       ALTER [ COLUMN ] column SET STATISTICS integer
95   ALTER TABLE [ ONLY ] table [ * ]
96       RENAME [ COLUMN ] column TO newcolumn
97   ALTER TABLE table
98       RENAME TO new_table
99   ALTER TABLE table
100       ADD table_constraint_definition
101   ALTER TABLE [ ONLY ] table 
102           DROP CONSTRAINT constraint { RESTRICT | CASCADE }
103   ALTER TABLE table
104           OWNER TO new_owner 
105
106 View table:
107
108     CREATE [ OR REPLACE ] VIEW view [ ( column name list ) ] AS SELECT query
109
110 =cut
111
112 use strict;
113 use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
114 $VERSION = sprintf "%d.%02d", q$Revision: 1.32 $ =~ /(\d+)\.(\d+)/;
115 $DEBUG   = 0 unless defined $DEBUG;
116
117 use Data::Dumper;
118 use Parse::RecDescent;
119 use Exporter;
120 use base qw(Exporter);
121
122 @EXPORT_OK = qw(parse);
123
124 # Enable warnings within the Parse::RecDescent module.
125 $::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
126 $::RD_WARN   = 1; # Enable warnings. This will warn on unused rules &c.
127 $::RD_HINT   = 1; # Give out hints to help fix problems.
128
129 my $parser; # should we do this?  There's no programmic way to 
130             # change the grammar, so I think this is safe.
131
132 $GRAMMAR = q!
133
134 { my ( %tables, $table_order, @table_comments) }
135
136 #
137 # The "eofile" rule makes the parser fail if any "statement" rule
138 # fails.  Otherwise, the first successful match by a "statement" 
139 # won't cause the failure needed to know that the parse, as a whole,
140 # failed. -ky
141 #
142 startrule : statement(s) eofile { \%tables }
143
144 eofile : /^\Z/
145
146 statement : create
147   | comment_on_table
148   | comment_on_column
149   | comment
150   | alter
151   | grant
152   | revoke
153   | drop
154   | insert
155   | connect
156   | update
157   | set
158   | <error>
159
160 connect : /^\s*\\\connect.*\n/
161
162 set : /set/i /[^;]*/ ';'
163
164 revoke : /revoke/i WORD(s /,/) /on/i TABLE(?) table_name /from/i name_with_opt_quotes(s /,/) ';'
165     {
166         my $table_name = $item{'table_name'};
167         push @{ $tables{ $table_name }{'permissions'} }, {
168             type       => 'revoke',
169             actions    => $item[2],
170             users      => $item[7],
171         }
172     }
173
174 grant : /grant/i WORD(s /,/) /on/i TABLE(?) table_name /to/i name_with_opt_quotes(s /,/) ';'
175     {
176         my $table_name = $item{'table_name'};
177         push @{ $tables{ $table_name }{'permissions'} }, {
178             type       => 'grant',
179             actions    => $item[2],
180             users      => $item[7],
181         }
182     }
183
184 drop : /drop/i /[^;]*/ ';'
185
186 insert : /insert/i /[^;]*/ ';'
187
188 update : /update/i /[^;]*/ ';'
189
190 #
191 # Create table.
192 #
193 create : create_table table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
194     {
195         my $table_name                       = $item{'table_name'};
196         $tables{ $table_name }{'order'}      = ++$table_order;
197         $tables{ $table_name }{'table_name'} = $table_name;
198
199         if ( @table_comments ) {
200             $tables{ $table_name }{'comments'} = [ @table_comments ];
201             @table_comments = ();
202         }
203
204         my $i = 1;
205         my @constraints;
206         for my $definition ( @{ $item[4] } ) {
207             if ( $definition->{'supertype'} eq 'field' ) {
208                 my $field_name = $definition->{'name'};
209                 $tables{ $table_name }{'fields'}{ $field_name } = 
210                     { %$definition, order => $i };
211                 $i++;
212                                 
213                 for my $constraint ( @{ $definition->{'constraints'} || [] } ) {
214                     $constraint->{'fields'} = [ $field_name ];
215                     push @{ $tables{ $table_name }{'constraints'} },
216                         $constraint;
217                 }
218             }
219             elsif ( $definition->{'supertype'} eq 'constraint' ) {
220                 push @{ $tables{ $table_name }{'constraints'} }, $definition;
221             }
222             elsif ( $definition->{'supertype'} eq 'index' ) {
223                 push @{ $tables{ $table_name }{'indices'} }, $definition;
224             }
225         }
226
227         for my $option ( @{ $item[6] } ) {
228             $tables{ $table_name }{'table_options(s?)'}{ $option->{'type'} } = 
229                 $option;
230         }
231
232         1;
233     }
234
235 create : /create/i unique(?) /(index|key)/i index_name /on/i table_name using_method(?) '(' field_name(s /,/) ')' where_predicate(?) ';'
236     {
237         push @{ $tables{ $item{'table_name'} }{'indices'} },
238             {
239                 name      => $item{'index_name'},
240                 supertype => $item{'unique'}[0] ? 'constraint' : 'index',
241                 type      => $item{'unique'}[0] ? 'unique'     : 'normal',
242                 fields    => $item[9],
243                 method    => $item{'using_method'}[0],
244             }
245         ;
246
247     }
248
249 #
250 # Create anything else (e.g., domain, function, etc.)
251 #
252 create : /create/i WORD /[^;]+/ ';'
253     { @table_comments = (); }
254
255 using_method : /using/i WORD { $item[2] }
256
257 where_predicate : /where/i /[^;]+/
258
259 create_definition : field
260     | table_constraint
261     | <error>
262
263 table_comment : comment
264     {
265         my $comment = $item[1];
266         $return     = $comment;
267         push @table_comments, $comment;
268     }
269
270 comment : /^\s*(?:#|-{2}).*\n/
271
272 comment_on_table : /comment/i /on/i /table/i table_name /is/i comment_phrase ';'
273     {
274         push @{ $tables{ $item{'table_name'} }{'comments'} }, $item{'comment_phrase'};
275     }
276
277 comment_on_column : /comment/i /on/i /column/i column_name /is/i comment_phrase ';'
278     {
279         my $table_name = $item[4]->{'table'};
280         my $field_name = $item[4]->{'field'};
281         push @{ $tables{ $table_name }{'fields'}{ $field_name }{'comments'} }, 
282             $item{'comment_phrase'};
283     }
284
285 column_name : NAME '.' NAME
286     { $return = { table => $item[1], field => $item[3] } }
287
288 comment_phrase : /'.*?'|NULL/ 
289     { 
290         my $val = $item[1];
291         $val =~ s/^'|'$//g;
292         $return = $val;
293     }
294
295 field : comment(s?) field_name data_type field_meta(s?) comment(s?)
296     {
297         my ( $default, @constraints, $is_pk );
298         my $null = 1;
299         for my $meta ( @{ $item[4] } ) {
300             if ( $meta->{'type'} eq 'default' ) {
301                 $default = $meta;
302                 next;
303             }
304             elsif ( $meta->{'type'} eq 'not_null' ) {
305                 $null = 0;
306 #                next;
307             }
308             elsif ( $meta->{'type'} eq 'primary_key' ) {
309                 $is_pk = 1;
310             }
311
312             push @constraints, $meta if $meta->{'supertype'} eq 'constraint';
313         }
314
315         my @comments = ( @{ $item[1] }, @{ $item[5] } );
316
317         $return = {
318             supertype         => 'field',
319             name              => $item{'field_name'}, 
320             data_type         => $item{'data_type'}{'type'},
321             size              => $item{'data_type'}{'size'},
322             null              => $null,
323             default           => $default->{'value'},
324             constraints       => [ @constraints ],
325             comments          => [ @comments ],
326             is_primary_key    => $is_pk || 0,
327             is_auto_increment => $item{'data_type'}{'is_auto_increment'},
328         } 
329     }
330     | <error>
331
332 field_meta : default_val
333     | column_constraint
334
335 column_constraint : constraint_name(?) column_constraint_type deferrable(?) deferred(?)
336     {
337         my $desc       = $item{'column_constraint_type'};
338         my $type       = $desc->{'type'};
339         my $fields     = $desc->{'fields'}     || [];
340         my $expression = $desc->{'expression'} || '';
341
342         $return              =  {
343             supertype        => 'constraint',
344             name             => $item{'constraint_name'}[0] || '',
345             type             => $type,
346             expression       => $type eq 'check' ? $expression : '',
347             deferrable       => $item{'deferrable'},
348             deferred         => $item{'deferred'},
349             reference_table  => $desc->{'reference_table'},
350             reference_fields => $desc->{'reference_fields'},
351             match_type       => $desc->{'match_type'},
352             on_delete_do     => $desc->{'on_delete_do'},
353             on_update_do     => $desc->{'on_update_do'},
354         } 
355     }
356
357 constraint_name : /constraint/i name_with_opt_quotes { $item[2] }
358
359 column_constraint_type : /not null/i { $return = { type => 'not_null' } }
360     |
361     /null/i
362         { $return = { type => 'null' } }
363     |
364     /unique/i
365         { $return = { type => 'unique' } }
366     |
367     /primary key/i 
368         { $return = { type => 'primary_key' } }
369     |
370     /check/i '(' /[^)]+/ ')' 
371         { $return = { type => 'check', expression => $item[3] } }
372     |
373     /references/i table_name parens_word_list(?) match_type(?) key_action(s?)
374     {
375         my ( $on_delete, $on_update );
376         for my $action ( @{ $item[5] || [] } ) {
377             $on_delete = $action->{'action'} if $action->{'type'} eq 'delete';
378             $on_update = $action->{'action'} if $action->{'type'} eq 'update';
379         }
380
381         $return              =  {
382             type             => 'foreign_key',
383             reference_table  => $item[2],
384             reference_fields => $item[3][0],
385             match_type       => $item[4][0],
386             on_delete_do     => $on_delete,
387             on_update_do     => $on_update,
388         }
389     }
390
391 table_name : name_with_opt_quotes
392
393 field_name : name_with_opt_quotes
394
395 name_with_opt_quotes : double_quote(?) NAME double_quote(?) { $item[2] }
396
397 double_quote: /"/
398
399 index_name : WORD
400
401 data_type : pg_data_type parens_value_list(?)
402     { 
403         my $data_type = $item[1];
404
405         #
406         # We can deduce some sizes from the data type's name.
407         #
408         $data_type->{'size'} ||= $item[2][0];
409
410         $return  = $data_type;
411     }
412
413 pg_data_type :
414     /(bigint|int8)/i
415         { 
416             $return = { 
417                 type => 'integer',
418                 size => 20,
419             };
420         }
421     |
422     /(smallint|int2)/i
423         { 
424             $return = {
425                 type => 'integer', 
426                 size => 5,
427             };
428         }
429     |
430     /(integer|int4?)/i # interval must come before this
431         { 
432             $return = {
433                 type => 'integer', 
434                 size => 10,
435             };
436         }
437     |    
438     /(real|float4)/i
439         { 
440             $return = {
441                 type => 'real', 
442                 size => 10,
443             };
444         }
445     |
446     /(double precision|float8?)/i
447         { 
448             $return = {
449                 type => 'float', 
450                 size => 20,
451             }; 
452         }
453     |
454     /(bigserial|serial8)/i
455         { 
456             $return = { 
457                 type              => 'integer', 
458                 size              => 20, 
459                 is_auto_increment => 1,
460             };
461         }
462     |
463     /serial4?/i
464         { 
465             $return = { 
466                 type              => 'integer',
467                 size              => 11, 
468                 is_auto_increment => 1,
469             };
470         }
471     |
472     /(bit varying|varbit)/i
473         { 
474             $return = { type => 'varbit' };
475         }
476     |
477     /character varying/i
478         { 
479             $return = { type => 'varchar' };
480         }
481     |
482     /char(acter)?/i
483         { 
484             $return = { type => 'char' };
485         }
486     |
487     /bool(ean)?/i
488         { 
489             $return = { type => 'boolean' };
490         }
491     |
492     /bytea/i
493         { 
494             $return = { type => 'bytea' };
495         }
496     |
497     /(timestamptz|timestamp)/i
498         { 
499             $return = { type => 'timestamp' };
500         }
501     |
502     /text/i
503         { 
504             $return = { 
505                 type => 'text',
506                 size => 64_000,
507             };
508         }
509     |
510     /(bit|box|cidr|circle|date|inet|interval|line|lseg|macaddr|money|numeric|decimal|path|point|polygon|timetz|time|varchar)/i
511         { 
512             $return = { type => $item[1] };
513         }
514
515 parens_value_list : '(' VALUE(s /,/) ')'
516     { $item[2] }
517
518 parens_word_list : '(' WORD(s /,/) ')'
519     { $item[2] }
520
521 field_size : '(' num_range ')' { $item{'num_range'} }
522
523 num_range : DIGITS ',' DIGITS
524     { $return = $item[1].','.$item[3] }
525     | DIGITS
526     { $return = $item[1] }
527
528 table_constraint : comment(s?) constraint_name(?) table_constraint_type deferrable(?) deferred(?) comment(s?)
529     {
530         my $desc       = $item{'table_constraint_type'};
531         my $type       = $desc->{'type'};
532         my $fields     = $desc->{'fields'};
533         my $expression = $desc->{'expression'};
534         my @comments   = ( @{ $item[1] }, @{ $item[-1] } );
535
536         $return              =  {
537             name             => $item{'constraint_name'}[0] || '',
538             supertype        => 'constraint',
539             type             => $type,
540             fields           => $type ne 'check' ? $fields : [],
541             expression       => $type eq 'check' ? $expression : '',
542             deferrable       => $item{'deferrable'},
543             deferred         => $item{'deferred'},
544             reference_table  => $desc->{'reference_table'},
545             reference_fields => $desc->{'reference_fields'},
546             match_type       => $desc->{'match_type'}[0],
547             on_delete_do     => $desc->{'on_delete_do'},
548             on_update_do     => $desc->{'on_update_do'},
549             comments         => [ @comments ],
550         } 
551     }
552
553 table_constraint_type : /primary key/i '(' name_with_opt_quotes(s /,/) ')' 
554     { 
555         $return = {
556             type   => 'primary_key',
557             fields => $item[3],
558         }
559     }
560     |
561     /unique/i '(' name_with_opt_quotes(s /,/) ')' 
562     { 
563         $return    =  {
564             type   => 'unique',
565             fields => $item[3],
566         }
567     }
568     |
569     /check/i '(' /[^)]+/ ')' 
570     {
571         $return        =  {
572             type       => 'check',
573             expression => $item[3],
574         }
575     }
576     |
577     /foreign key/i '(' name_with_opt_quotes(s /,/) ')' /references/i table_name parens_word_list(?) match_type(?) key_action(s?)
578     {
579         my ( $on_delete, $on_update );
580         for my $action ( @{ $item[9] || [] } ) {
581             $on_delete = $action->{'action'} if $action->{'type'} eq 'delete';
582             $on_update = $action->{'action'} if $action->{'type'} eq 'update';
583         }
584         
585         $return              =  {
586             supertype        => 'constraint',
587             type             => 'foreign_key',
588             fields           => $item[3],
589             reference_table  => $item[6],
590             reference_fields => $item[7][0],
591             match_type       => $item[8][0],
592             on_delete_do     => $on_delete || '',
593             on_update_do     => $on_update || '',
594         }
595     }
596
597 deferrable : /not/i /deferrable/i 
598     { 
599         $return = ( $item[1] =~ /not/i ) ? 0 : 1;
600     }
601
602 deferred : /initially/i /(deferred|immediate)/i { $item[2] }
603
604 match_type : /match full/i { 'match_full' }
605     |
606     /match partial/i { 'match_partial' }
607
608 key_action : key_delete 
609     |
610     key_update
611
612 key_delete : /on delete/i key_mutation
613     { 
614         $return => { 
615             type   => 'delete',
616             action => $item[2],
617         };
618     }
619
620 key_update : /on update/i key_mutation
621     { 
622         $return => { 
623             type   => 'update',
624             action => $item[2],
625         };
626     }
627
628 key_mutation : /no action/i { $return = 'no_action' }
629     |
630     /restrict/i { $return = 'restrict' }
631     |
632     /cascade/i { $return = 'cascade' }
633     |
634     /set null/i { $return = 'set null' }
635     |
636     /set default/i { $return = 'set default' }
637
638 alter : alter_table table_name /add/i table_constraint ';' 
639     { 
640         my $table_name = $item[2];
641         my $constraint = $item[4];
642         push @{ $tables{ $table_name }{'constraints'} }, $constraint;
643     }
644
645 alter_table : /alter/i /table/i only(?)
646
647 only : /only/i
648
649 create_table : /create/i TABLE
650
651 create_index : /create/i /index/i
652
653 default_val  : /default/i /(\d+|'[^']*'|\w+\(.*?\))|\w+/
654     { 
655         my $val =  defined $item[2] ? $item[2] : '';
656         $val    =~ s/^'|'$//g; 
657         $return =  {
658             supertype => 'constraint',
659             type      => 'default',
660             value     => $val,
661         }
662     }
663     | /null/i
664     { 
665         $return =  {
666             supertype => 'constraint',
667             type      => 'default',
668             value     => 'NULL',
669         }
670     }
671
672 name_with_opt_paren : NAME parens_value_list(s?)
673     { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
674
675 unique : /unique/i { 1 }
676
677 key : /key/i | /index/i
678
679 table_option : /inherits/i '(' name_with_opt_quotes(s /,/) ')'
680     { 
681         $return = { type => 'inherits', table_name => $item[3] }
682     }
683     |
684     /with(out)? oids/i
685     {
686         $return = { type => $item[1] =~ /out/i ? 'without_oids' : 'with_oids' }
687     }
688
689 TABLE : /table/i
690
691 SEMICOLON : /\s*;\n?/
692
693 WORD : /\w+/
694
695 DIGITS : /\d+/
696
697 COMMA : ','
698
699 NAME    : "`" /\w+/ "`"
700     { $item[2] }
701     | /\w+/
702     { $item[1] }
703     | /[\$\w]+/
704     { $item[1] }
705
706 VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
707     { $item[1] }
708     | /'.*?'/   # XXX doesn't handle embedded quotes
709     { $item[1] }
710     | /null/i
711     { 'NULL' }
712
713 !;
714
715 # -------------------------------------------------------------------
716 sub parse {
717     my ( $translator, $data ) = @_;
718     $parser ||= Parse::RecDescent->new($GRAMMAR);
719
720     $::RD_TRACE  = $translator->trace ? 1 : undef;
721     $DEBUG       = $translator->debug;
722
723     unless (defined $parser) {
724         return $translator->error("Error instantiating Parse::RecDescent ".
725             "instance: Bad grammer");
726     }
727
728     my $result = $parser->startrule($data);
729     die "Parse failed.\n" unless defined $result;
730     warn Dumper($result) if $DEBUG;
731
732     my $schema = $translator->schema;
733     my @tables = sort { 
734         $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
735     } keys %{ $result };
736
737     for my $table_name ( @tables ) {
738         my $tdata =  $result->{ $table_name };
739         my $table =  $schema->add_table( 
740             name  => $tdata->{'table_name'},
741         ) or die "Couldn't create table '$table_name': " . $schema->error;
742
743         my @fields = sort { 
744             $tdata->{'fields'}->{ $a }->{'order'} 
745             <=>
746             $tdata->{'fields'}->{ $b }->{'order'}
747         } keys %{ $tdata->{'fields'} };
748
749         for my $fname ( @fields ) {
750             my $fdata = $tdata->{'fields'}{ $fname };
751             my $field = $table->add_field(
752                 name              => $fdata->{'name'},
753                 data_type         => $fdata->{'data_type'},
754                 size              => $fdata->{'size'},
755                 default_value     => $fdata->{'default'},
756                 is_auto_increment => $fdata->{'is_auto_increment'},
757                 is_nullable       => $fdata->{'null'},
758             ) or die $table->error;
759
760             $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
761
762             for my $cdata ( @{ $fdata->{'constraints'} } ) {
763                 next unless $cdata->{'type'} eq 'foreign_key';
764                 $cdata->{'fields'} ||= [ $field->name ];
765                 push @{ $tdata->{'constraints'} }, $cdata;
766             }
767         }
768
769         for my $idata ( @{ $tdata->{'indices'} || [] } ) {
770             my $index  =  $table->add_index(
771                 name   => $idata->{'name'},
772                 type   => uc $idata->{'type'},
773                 fields => $idata->{'fields'},
774             ) or die $table->error;
775         }
776
777         for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
778             my $constraint       =  $table->add_constraint(
779                 name             => $cdata->{'name'},
780                 type             => $cdata->{'type'},
781                 fields           => $cdata->{'fields'},
782                 reference_table  => $cdata->{'reference_table'},
783                 reference_fields => $cdata->{'reference_fields'},
784                 match_type       => $cdata->{'match_type'} || '',
785                 on_delete        => $cdata->{'on_delete_do'},
786                 on_update        => $cdata->{'on_update_do'},
787                 expression       => $cdata->{'expression'},
788             ) or die "Can't add constraint of type '" .
789                 $cdata->{'type'} .  "' to table '" . $table->name . 
790                 "': " . $table->error;
791         }
792     }
793
794     return 1;
795 }
796
797 1;
798
799 # -------------------------------------------------------------------
800 # Rescue the drowning and tie your shoestrings.
801 # Henry David Thoreau 
802 # -------------------------------------------------------------------
803
804 =pod
805
806 =head1 AUTHORS
807
808 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
809 Allen Day E<lt>allenday@ucla.eduE<gt>.
810
811 =head1 SEE ALSO
812
813 perl(1), Parse::RecDescent.
814
815 =cut