File Coverage

blib/lib/SQL/Translator/Parser/PostgreSQL.pm
Criterion Covered Total %
statement 68 68 100.0
branch 32 40 80.0
condition 9 14 64.2
subroutine 6 6 100.0
pod 0 1 0.0
total 115 129 89.1


line stmt bran cond sub pod time code
1             package SQL::Translator::Parser::PostgreSQL;
2              
3             =head1 NAME
4              
5             SQL::Translator::Parser::PostgreSQL - parser for PostgreSQL
6              
7             =head1 SYNOPSIS
8              
9             use SQL::Translator;
10             use SQL::Translator::Parser::PostgreSQL;
11              
12             my $translator = SQL::Translator->new;
13             $translator->parser("SQL::Translator::Parser::PostgreSQL");
14              
15             =head1 DESCRIPTION
16              
17             The grammar was started from the MySQL parsers. Here is the description
18             from PostgreSQL, truncated to what's currently supported (patches welcome, of course) :
19              
20             Table:
21             (http://www.postgresql.org/docs/current/sql-createtable.html)
22              
23             CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
24             { column_name data_type [ DEFAULT default_expr ]
25             [ column_constraint [, ... ] ]
26             | table_constraint } [, ... ]
27             )
28             [ INHERITS ( parent_table [, ... ] ) ]
29             [ WITH OIDS | WITHOUT OIDS ]
30              
31             where column_constraint is:
32              
33             [ CONSTRAINT constraint_name ]
34             { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
35             CHECK (expression) |
36             REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
37             [ ON DELETE action ] [ ON UPDATE action ] }
38             [ DEFERRABLE | NOT DEFERRABLE ]
39             [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
40              
41             and table_constraint is:
42              
43             [ CONSTRAINT constraint_name ]
44             { UNIQUE ( column_name [, ... ] ) |
45             PRIMARY KEY ( column_name [, ... ] ) |
46             CHECK ( expression ) |
47             FOREIGN KEY ( column_name [, ... ] )
48             REFERENCES reftable [ ( refcolumn [, ... ] ) ]
49             [ MATCH FULL | MATCH PARTIAL ]
50             [ ON DELETE action ] [ ON UPDATE action ] }
51             [ DEFERRABLE | NOT DEFERRABLE ]
52             [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
53              
54             Index :
55             (http://www.postgresql.org/docs/current/sql-createindex.html)
56              
57             CREATE [ UNIQUE ] INDEX index_name ON table
58             [ USING acc_method ] ( column [ ops_name ] [, ...] )
59             [ INCLUDE ( column [, ...] ) ]
60             [ WHERE predicate ]
61             CREATE [ UNIQUE ] INDEX index_name ON table
62             [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
63             [ WHERE predicate ]
64              
65             Alter table:
66              
67             ALTER TABLE [ ONLY ] table [ * ]
68             ADD [ COLUMN ] column type [ column_constraint [ ... ] ]
69             ALTER TABLE [ ONLY ] table [ * ]
70             ALTER [ COLUMN ] column { SET DEFAULT value | DROP DEFAULT }
71             ALTER TABLE [ ONLY ] table [ * ]
72             ALTER [ COLUMN ] column SET STATISTICS integer
73             ALTER TABLE [ ONLY ] table [ * ]
74             RENAME [ COLUMN ] column TO newcolumn
75             ALTER TABLE table
76             RENAME TO new_table
77             ALTER TABLE table
78             ADD table_constraint_definition
79             ALTER TABLE [ ONLY ] table
80             DROP CONSTRAINT constraint { RESTRICT | CASCADE }
81             ALTER TABLE table
82             OWNER TO new_owner
83              
84             View :
85              
86             CREATE [ OR REPLACE ] VIEW view [ ( column name list ) ] AS SELECT query
87              
88             =cut
89              
90 3     3   1603 use strict;
  3         7  
  3         83  
91 3     3   20 use warnings;
  3         6  
  3         155  
92              
93             our $VERSION = '1.6_3';
94              
95             our $DEBUG;
96             $DEBUG = 0 unless defined $DEBUG;
97              
98 3     3   17 use Data::Dumper;
  3         4  
  3         143  
99 3     3   16 use SQL::Translator::Utils qw/ddl_parser_instance/;
  3         7  
  3         126  
100              
101 3     3   16 use base qw(Exporter);
  3         5  
  3         3007  
102             our @EXPORT_OK = qw(parse);
103              
104             our $GRAMMAR = <<'END_OF_GRAMMAR';
105              
106             { my ( %tables, @views, @triggers, $table_order, $field_order, @table_comments) }
107              
108             #
109             # The "eofile" rule makes the parser fail if any "statement" rule
110             # fails. Otherwise, the first successful match by a "statement"
111             # won't cause the failure needed to know that the parse, as a whole,
112             # failed. -ky
113             #
114             startrule : statement(s) eofile {
115             {
116             tables => \%tables,
117             views => \@views,
118             triggers => \@triggers,
119             }
120             }
121              
122             eofile : /^\Z/
123              
124             statement : create
125             | comment_on_table
126             | comment_on_column
127             | comment_on_other
128             | comment
129             | alter
130             | grant
131             | revoke
132             | drop
133             | insert
134             | connect
135             | update
136             | set
137             | select
138             | copy
139             | readin_symbol
140             | commit
141             |
142              
143             commit : /commit/i ';'
144              
145             connect : /^\s*\\connect.*\n/
146              
147             set : /set/i /[^;]*/ ';'
148              
149             revoke : /revoke/i WORD(s /,/) /on/i TABLE(?) table_id /from/i NAME(s /,/) ';'
150             {
151             my $table_info = $item{'table_id'};
152             my $schema_name = $table_info->{'schema_name'};
153             my $table_name = $table_info->{'table_name'};
154             push @{ $tables{ $table_name }{'permissions'} }, {
155             type => 'revoke',
156             actions => $item[2],
157             users => $item[7],
158             }
159             }
160              
161             revoke : /revoke/i WORD(s /,/) /on/i SCHEMA(?) schema_name /from/i NAME(s /,/) ';'
162             { 1 }
163              
164             grant : /grant/i WORD(s /,/) /on/i TABLE(?) table_id /to/i NAME(s /,/) ';'
165             {
166             my $table_info = $item{'table_id'};
167             my $schema_name = $table_info->{'schema_name'};
168             my $table_name = $table_info->{'table_name'};
169             push @{ $tables{ $table_name }{'permissions'} }, {
170             type => 'grant',
171             actions => $item[2],
172             users => $item[7],
173             }
174             }
175              
176             grant : /grant/i WORD(s /,/) /on/i SCHEMA(?) schema_name /to/i NAME(s /,/) ';'
177             { 1 }
178              
179             drop : /drop/i /[^;]*/ ';'
180              
181             string :
182             /'(\.|''|[^\\'])*'/
183              
184             nonstring : /[^;\'"]+/
185              
186             statement_body : string | nonstring
187              
188             insert : /insert/i statement_body(s?) ';'
189              
190             update : /update/i statement_body(s?) ';'
191              
192             #
193             # Create table.
194             #
195             create : CREATE temporary(?) TABLE table_id '(' create_definition(s? /,/) ')' table_option(s?) ';'
196             {
197             my $table_info = $item{'table_id'};
198             my $schema_name = $table_info->{'schema_name'};
199             my $table_name = $table_info->{'table_name'};
200             $tables{ $table_name }{'order'} = ++$table_order;
201             $tables{ $table_name }{'schema_name'} = $schema_name;
202             $tables{ $table_name }{'table_name'} = $table_name;
203              
204             $tables{ $table_name }{'temporary'} = $item[2][0];
205              
206             if ( @table_comments ) {
207             $tables{ $table_name }{'comments'} = [ @table_comments ];
208             @table_comments = ();
209             }
210              
211             my @constraints;
212             for my $definition ( @{ $item[6] } ) {
213             if ( $definition->{'supertype'} eq 'field' ) {
214             my $field_name = $definition->{'name'};
215             $tables{ $table_name }{'fields'}{ $field_name } =
216             { %$definition, order => $field_order++ };
217              
218             for my $constraint ( @{ $definition->{'constraints'} || [] } ) {
219             $constraint->{'fields'} = [ $field_name ];
220             push @{ $tables{ $table_name }{'constraints'} },
221             $constraint;
222             }
223             }
224             elsif ( $definition->{'supertype'} eq 'constraint' ) {
225             push @{ $tables{ $table_name }{'constraints'} }, $definition;
226             }
227             elsif ( $definition->{'supertype'} eq 'index' ) {
228             push @{ $tables{ $table_name }{'indices'} }, $definition;
229             }
230             }
231              
232             for my $option ( @{ $item[8] } ) {
233             $tables{ $table_name }{'table_options(s?)'}{ $option->{'type'} } =
234             $option;
235             }
236              
237             1;
238             }
239              
240             create : CREATE unique(?) /(index|key)/i index_name /on/i table_id using_method(?) '(' field_name(s /,/) ')' include_covering(?) where_predicate(?) ';'
241             {
242             my $table_info = $item{'table_id'};
243             my $schema_name = $table_info->{'schema_name'};
244             my $table_name = $table_info->{'table_name'};
245             push @{ $tables{ $table_name }{'indices'} },
246             {
247             name => $item{'index_name'},
248             supertype => $item{'unique'}[0] ? 'constraint' : 'index',
249             type => $item{'unique'}[0] ? 'unique' : 'normal',
250             fields => $item[9],
251             method => $item{'using_method(?)'}[0],
252             where => $item{'where_predicate(?)'}[0],
253             include => $item{'include_covering(?)'}[0]
254             }
255             ;
256             }
257              
258             create : CREATE or_replace(?) temporary(?) VIEW view_id view_fields(?) /AS/i view_target ';'
259             {
260             push @views, {
261             schema_name => $item{view_id}{schema_name},
262             view_name => $item{view_id}{view_name},
263             sql => $item{view_target},
264             fields => $item[6],
265             is_temporary => $item[3][0],
266             }
267             }
268              
269             create: CREATE /MATERIALIZED VIEW/i if_not_exists(?) view_id view_fields(?) /AS/i view_target ';'
270             {
271             push @views, {
272             schema_name => $item{view_id}{schema_name},
273             view_name => $item{view_id}{view_name},
274             sql => $item{view_target},
275             fields => $item[5],
276             extra => { materialized => 1 }
277             }
278             }
279              
280             if_not_exists : /IF NOT EXISTS/i
281              
282             trigger_name : NAME
283              
284             trigger_scope : /FOR/i /EACH/i /(ROW|STATEMENT)/i { $return = lc $1 }
285              
286             before_or_after : /(before|after)/i { $return = lc $1 }
287              
288             trigger_action : /.+/
289              
290             database_event : /insert|update|delete/i
291             database_events : database_event(s /OR/)
292              
293             create : CREATE /TRIGGER/i trigger_name before_or_after database_events /ON/i table_id trigger_scope(?) trigger_action
294             {
295             # Hack to pass roundtrip tests which have trigger statements terminated by double semicolon
296             # and expect the returned data to have the same
297             my $action = $item{trigger_action};
298             $action =~ s/;$//;
299              
300             push @triggers, {
301             name => $item{trigger_name},
302             perform_action_when => $item{before_or_after},
303             database_events => $item{database_events},
304             on_table => $item{table_id}{table_name},
305             scope => $item{'trigger_scope(?)'}[0],
306             action => $action,
307             }
308             }
309              
310             #
311             # Create anything else (e.g., domain, etc.)
312             #
313             create : CREATE WORD /[^;]+/ ';'
314             { @table_comments = (); }
315              
316             using_method : /using/i WORD { $item[2] }
317              
318             where_predicate : /where/i /[^;]+/
319              
320             include_covering : /include/i '(' covering_field_name(s /,/) ')'
321             { $item{'covering_field_name(s)'} }
322              
323             create_definition : field
324             | table_constraint
325             |
326              
327             comment : /^\s*(?:#|-{2})(.*)\n/
328             {
329             my $comment = $item[1];
330             $comment =~ s/^\s*(#|-*)\s*//;
331             $comment =~ s/\s*$//;
332             $return = $comment;
333             push @table_comments, $comment;
334             }
335              
336             comment_on_table : /comment/i /on/i /table/i table_id /is/i comment_phrase ';'
337             {
338             my $table_info = $item{'table_id'};
339             my $schema_name = $table_info->{'schema_name'};
340             my $table_name = $table_info->{'table_name'};
341             push @{ $tables{ $table_name }{'comments'} }, $item{'comment_phrase'};
342             }
343              
344             comment_on_column : /comment/i /on/i /column/i column_name /is/i comment_phrase ';'
345             {
346             my $table_name = $item[4]->{'table'};
347             my $field_name = $item[4]->{'field'};
348             if ($tables{ $table_name }{'fields'}{ $field_name } ) {
349             push @{ $tables{ $table_name }{'fields'}{ $field_name }{'comments'} },
350             $item{'comment_phrase'};
351             }
352             else {
353             die "No such column as $table_name.$field_name";
354             }
355             }
356              
357             comment_on_other : /comment/i /on/i /\w+/ /\w+/ /is/i comment_phrase ';'
358             {
359             push(@table_comments, $item{'comment_phrase'});
360             }
361              
362             # [added by cjm 20041019]
363             # [TODO: other comment-on types]
364             # for now we just have a general mechanism for handling other
365             # kinds of comments than table/column; I'm not sure of the best
366             # way to incorporate these into the datamodel
367             #
368             # this is the exhaustive list of types of comment:
369             #COMMENT ON DATABASE my_database IS 'Development Database';
370             #COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee id';
371             #COMMENT ON RULE my_rule IS 'Logs UPDATES of employee records';
372             #COMMENT ON SEQUENCE my_sequence IS 'Used to generate primary keys';
373             #COMMENT ON TABLE my_table IS 'Employee Information';
374             #COMMENT ON TYPE my_type IS 'Complex Number support';
375             #COMMENT ON VIEW my_view IS 'View of departmental costs';
376             #COMMENT ON COLUMN my_table.my_field IS 'Employee ID number';
377             #COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for R.I.';
378             #
379             # this is tested by test 08
380              
381             column_name : NAME '.' NAME
382             { $return = { table => $item[1], field => $item[3] } }
383              
384             comment_phrase : /null/i
385             { $return = 'NULL' }
386             | SQSTRING
387             | DOLLARSTRING
388              
389             field : field_comment(s?) field_name data_type field_meta(s?) field_comment(s?)
390             {
391             my ( $default, @constraints, $is_pk );
392             my $is_nullable = 1;
393             for my $meta ( @{ $item[4] } ) {
394             if ( $meta->{'type'} eq 'default' ) {
395             $default = $meta;
396             next;
397             }
398             elsif ( $meta->{'type'} eq 'not_null' ) {
399             $is_nullable = 0;
400             }
401             elsif ( $meta->{'type'} eq 'primary_key' ) {
402             $is_pk = 1;
403             }
404              
405             push @constraints, $meta if $meta->{'supertype'} eq 'constraint';
406             }
407              
408             my @comments = ( @{ $item[1] }, @{ $item[5] } );
409              
410             $return = {
411             supertype => 'field',
412             name => $item{'field_name'},
413             data_type => $item{'data_type'}{'type'},
414             size => $item{'data_type'}{'size'},
415             is_nullable => $is_nullable,
416             default => $default->{'value'},
417             constraints => [ @constraints ],
418             comments => [ @comments ],
419             is_primary_key => $is_pk || 0,
420             is_auto_increment => $item{'data_type'}{'is_auto_increment'},
421             }
422             }
423             |
424              
425             field_comment : /^\s*(?:#|-{2})(.*)\n/
426             {
427             my $comment = $item[1];
428             $comment =~ s/^\s*(#|-*)\s*//;
429             $comment =~ s/\s*$//;
430             $return = $comment;
431             }
432              
433             field_meta : default_val
434             | column_constraint
435              
436             view_fields : '(' field_name(s /,/) ')'
437             { $return = join (',', @{$item[2]} ) }
438              
439             column_constraint : constraint_name(?) column_constraint_type deferrable(?) deferred(?)
440             {
441             my $desc = $item{'column_constraint_type'};
442             my $type = $desc->{'type'};
443             my $fields = $desc->{'fields'} || [];
444             my $expression = $desc->{'expression'} || '';
445              
446             $return = {
447             supertype => 'constraint',
448             name => $item{'constraint_name'}[0] || '',
449             type => $type,
450             expression => $type eq 'check' ? $expression : '',
451             deferrable => $item{'deferrable'},
452             deferred => $item{'deferred'},
453             reference_table => $desc->{'reference_table'},
454             reference_fields => $desc->{'reference_fields'},
455             match_type => $desc->{'match_type'},
456             on_delete => $desc->{'on_delete'} || $desc->{'on_delete_do'},
457             on_update => $desc->{'on_update'} || $desc->{'on_update_do'},
458             }
459             }
460              
461             constraint_name : /constraint/i NAME { $item[2] }
462              
463             column_constraint_type : /not null/i { $return = { type => 'not_null' } }
464             |
465             /null/i
466             { $return = { type => 'null' } }
467             |
468             /unique/i
469             { $return = { type => 'unique' } }
470             |
471             /primary key/i
472             { $return = { type => 'primary_key' } }
473             |
474             /check/i '(' /[^)]+/ ')'
475             { $return = { type => 'check', expression => $item[3] } }
476             |
477             /references/i table_id parens_word_list(?) match_type(?) key_action(s?)
478             {
479             my $table_info = $item{'table_id'};
480             my $schema_name = $table_info->{'schema_name'};
481             my $table_name = $table_info->{'table_name'};
482             my ( $on_delete, $on_update );
483             for my $action ( @{ $item[5] || [] } ) {
484             $on_delete = $action->{'action'} if $action->{'type'} eq 'delete';
485             $on_update = $action->{'action'} if $action->{'type'} eq 'update';
486             }
487              
488             $return = {
489             type => 'foreign_key',
490             reference_table => $table_name,
491             reference_fields => $item[3][0],
492             match_type => $item[4][0],
493             on_delete => $on_delete,
494             on_update => $on_update,
495             }
496             }
497              
498             table_id : schema_qualification(?) NAME {
499             $return = { schema_name => $item[1][0], table_name => $item[2] }
500             }
501              
502             view_id : schema_qualification(?) NAME {
503             $return = { schema_name => $item[1][0], view_name => $item[2] }
504             }
505              
506             view_target : /select|with/i /[^;]+/ {
507             $return = "$item[1] $item[2]";
508             }
509              
510             # SELECT views _may_ support outer parens, and we used to produce
511             # such sql, although non-standard. Use ugly lookeahead to parse
512             view_target : '(' /select/i / [^;]+ (?= \) ) /x ')' {
513             $return = "$item[2] $item[3]"
514             }
515              
516             view_target_spec :
517              
518             schema_qualification : NAME '.'
519              
520             schema_name : NAME
521              
522             field_name : NAME
523              
524             covering_field_name : NAME
525              
526             double_quote: /"/
527              
528             index_name : NAME
529              
530             array_indicator : '[' ']'
531             { $return = $item[1].$item[2] }
532              
533             data_type : pg_data_type parens_value_list(?) array_indicator(?)
534             {
535             my $data_type = $item[1];
536              
537             $data_type->{type} .= $item[3][0] if $item[3][0];
538              
539             #
540             # We can deduce some sizes from the data type's name.
541             #
542             if ( my @size = @{$item[2]} ) {
543             $data_type->{'size'} = (@size == 1 ? $size[0] : \@size);
544             }
545              
546             $return = $data_type;
547             }
548              
549             pg_data_type :
550             /(bigint|int8)/i
551             {
552             $return = {
553             type => 'integer',
554             size => 20,
555             };
556             }
557             |
558             /(smallint|int2)/i
559             {
560             $return = {
561             type => 'integer',
562             size => 5,
563             };
564             }
565             |
566             /interval/i
567             {
568             $return = { type => 'interval' };
569             }
570             |
571             /(integer|int4?)/i # interval must come before this
572             {
573             $return = {
574             type => 'integer',
575             size => 10,
576             };
577             }
578             |
579             /(real|float4)/i
580             {
581             $return = {
582             type => 'real',
583             size => 10,
584             };
585             }
586             |
587             /(double precision|float8?)/i
588             {
589             $return = {
590             type => 'float',
591             size => 20,
592             };
593             }
594             |
595             /(bigserial|serial8)/i
596             {
597             $return = {
598             type => 'integer',
599             size => 20,
600             is_auto_increment => 1,
601             };
602             }
603             |
604             /serial4?/i
605             {
606             $return = {
607             type => 'integer',
608             size => 11,
609             is_auto_increment => 1,
610             };
611             }
612             |
613             /(bit varying|varbit)/i
614             {
615             $return = { type => 'varbit' };
616             }
617             |
618             /character varying/i
619             {
620             $return = { type => 'varchar' };
621             }
622             |
623             /char(acter)?/i
624             {
625             $return = { type => 'char' };
626             }
627             |
628             /bool(ean)?/i
629             {
630             $return = { type => 'boolean' };
631             }
632             |
633             /bytea/i
634             {
635             $return = { type => 'bytea' };
636             }
637             |
638             / ( timestamp (?:tz)? ) (?: \( \d \) )? ( \s with (?:out)? \s time \s zone )? /ix
639             {
640             $return = { type => 'timestamp' . ($2||'') };
641             }
642             |
643             / ( time (?:tz)? ) (?: \( \d \) )? ( \s with (?:out)? \s time \s zone )? /ix
644             {
645             $return = { type => 'time' . ($2||'') };
646             }
647             |
648             /text/i
649             {
650             $return = {
651             type => 'text',
652             size => 64_000,
653             };
654             }
655             |
656             /(bit|box|cidr|circle|date|inet|line|lseg|macaddr|money|numeric|decimal|path|point|polygon|varchar|json|hstore|uuid)/i
657             {
658             $return = { type => $item[1] };
659             }
660              
661             parens_value_list : '(' VALUE(s /,/) ')'
662             { $item[2] }
663              
664              
665             parens_word_list : '(' NAME(s /,/) ')'
666             { $item[2] }
667              
668             field_size : '(' num_range ')' { $item{'num_range'} }
669              
670             num_range : DIGITS ',' DIGITS
671             { $return = $item[1].','.$item[3] }
672             | DIGITS
673             { $return = $item[1] }
674              
675             table_constraint : comment(s?) constraint_name(?) table_constraint_type deferrable(?) deferred(?) comment(s?)
676             {
677             my $desc = $item{'table_constraint_type'};
678             my $type = $desc->{'type'};
679             my $fields = $desc->{'fields'};
680             my $expression = $desc->{'expression'};
681             my @comments = ( @{ $item[1] }, @{ $item[-1] } );
682              
683             $return = {
684             name => $item[2][0] || '',
685             supertype => 'constraint',
686             type => $type,
687             fields => $type ne 'check' ? $fields : [],
688             expression => $type eq 'check' ? $expression : '',
689             deferrable => $item{'deferrable'},
690             deferred => $item{'deferred'},
691             reference_table => $desc->{'reference_table'},
692             reference_fields => $desc->{'reference_fields'},
693             match_type => $desc->{'match_type'},
694             on_delete => $desc->{'on_delete'} || $desc->{'on_delete_do'},
695             on_update => $desc->{'on_update'} || $desc->{'on_update_do'},
696             comments => [ @comments ],
697             }
698             }
699              
700             table_constraint_type : /primary key/i '(' NAME(s /,/) ')'
701             {
702             $return = {
703             type => 'primary_key',
704             fields => $item[3],
705             }
706             }
707             |
708             /unique/i '(' NAME(s /,/) ')'
709             {
710             $return = {
711             type => 'unique',
712             fields => $item[3],
713             }
714             }
715             |
716             /check/i '(' /[^)]+/ ')'
717             {
718             $return = {
719             type => 'check',
720             expression => $item[3],
721             }
722             }
723             |
724             /foreign key/i '(' NAME(s /,/) ')' /references/i table_id parens_word_list(?) match_type(?) key_action(s?)
725             {
726             my ( $on_delete, $on_update );
727             for my $action ( @{ $item[9] || [] } ) {
728             $on_delete = $action->{'action'} if $action->{'type'} eq 'delete';
729             $on_update = $action->{'action'} if $action->{'type'} eq 'update';
730             }
731              
732             $return = {
733             supertype => 'constraint',
734             type => 'foreign_key',
735             fields => $item[3],
736             reference_table => $item[6]->{'table_name'},
737             reference_fields => $item[7][0],
738             match_type => $item[8][0],
739             on_delete => $on_delete || '',
740             on_update => $on_update || '',
741             }
742             }
743              
744             deferrable : not(?) /deferrable/i
745             {
746             $return = ( $item[1] =~ /not/i ) ? 0 : 1;
747             }
748              
749             deferred : /initially/i /(deferred|immediate)/i { $item[2] }
750              
751             match_type : /match/i /partial|full|simple/i { $item[2] }
752              
753             key_action : key_delete
754             |
755             key_update
756              
757             key_delete : /on delete/i key_mutation
758             {
759             $return = {
760             type => 'delete',
761             action => $item[2],
762             };
763             }
764              
765             key_update : /on update/i key_mutation
766             {
767             $return = {
768             type => 'update',
769             action => $item[2],
770             };
771             }
772              
773             key_mutation : /no action/i { $return = 'no_action' }
774             |
775             /restrict/i { $return = 'restrict' }
776             |
777             /cascade/i { $return = 'cascade' }
778             |
779             /set null/i { $return = 'set null' }
780             |
781             /set default/i { $return = 'set default' }
782              
783             alter : alter_table table_id add_column field ';'
784             {
785             my $field_def = $item[4];
786             $tables{ $item[2]->{'table_name'} }{'fields'}{ $field_def->{'name'} } = {
787             %$field_def, order => $field_order++
788             };
789             1;
790             }
791              
792             alter : alter_table table_id ADD table_constraint ';'
793             {
794             my $table_name = $item[2]->{'table_name'};
795             my $constraint = $item[4];
796             push @{ $tables{ $table_name }{'constraints'} }, $constraint;
797             1;
798             }
799              
800             alter : alter_table table_id drop_column NAME restrict_or_cascade(?) ';'
801             {
802             $tables{ $item[2]->{'table_name'} }{'fields'}{ $item[4] }{'drop'} = 1;
803             1;
804             }
805              
806             alter : alter_table table_id alter_column NAME alter_default_val ';'
807             {
808             $tables{ $item[2]->{'table_name'} }{'fields'}{ $item[4] }{'default'} =
809             $item[5]->{'value'};
810             1;
811             }
812              
813             #
814             # These will just parse for now but won't affect the structure. - ky
815             #
816             alter : alter_table table_id /rename/i /to/i NAME ';'
817             { 1 }
818              
819             alter : alter_table table_id alter_column NAME SET /statistics/i INTEGER ';'
820             { 1 }
821              
822             alter : alter_table table_id alter_column NAME SET /storage/i storage_type ';'
823             { 1 }
824              
825             alter : alter_table table_id rename_column NAME /to/i NAME ';'
826             { 1 }
827              
828             alter : alter_table table_id DROP /constraint/i NAME restrict_or_cascade ';'
829             { 1 }
830              
831             alter : alter_table table_id /owner/i /to/i NAME ';'
832             { 1 }
833              
834             alter : alter_sequence NAME /owned/i /by/i column_name ';'
835             { 1 }
836              
837             storage_type : /(plain|external|extended|main)/i
838              
839             temporary : /temp(orary)?\b/i
840             {
841             1;
842             }
843              
844             or_replace : /or replace/i
845              
846             alter_default_val : SET default_val
847             {
848             $return = { value => $item[2]->{'value'} }
849             }
850             | DROP DEFAULT
851             {
852             $return = { value => undef }
853             }
854              
855             #
856             # This is a little tricky to get right, at least WRT to making the
857             # tests pass. The problem is that the constraints are stored just as
858             # a list (no name access), and the tests expect the constraints in a
859             # particular order. I'm going to leave the rule but disable the code
860             # for now. - ky
861             #
862             alter : alter_table table_id alter_column NAME alter_nullable ';'
863             {
864             # my $table_name = $item[2]->{'table_name'};
865             # my $field_name = $item[4];
866             # my $is_nullable = $item[5]->{'is_nullable'};
867             #
868             # $tables{ $table_name }{'fields'}{ $field_name }{'is_nullable'} =
869             # $is_nullable;
870             #
871             # if ( $is_nullable ) {
872             # 1;
873             # push @{ $tables{ $table_name }{'constraints'} }, {
874             # type => 'not_null',
875             # fields => [ $field_name ],
876             # };
877             # }
878             # else {
879             # for my $i (
880             # 0 .. $#{ $tables{ $table_name }{'constraints'} || [] }
881             # ) {
882             # my $c = $tables{ $table_name }{'constraints'}[ $i ] or next;
883             # my $fields = join( '', @{ $c->{'fields'} || [] } ) or next;
884             # if ( $c->{'type'} eq 'not_null' && $fields eq $field_name ) {
885             # delete $tables{ $table_name }{'constraints'}[ $i ];
886             # last;
887             # }
888             # }
889             # }
890              
891             1;
892             }
893              
894             alter_nullable : SET not_null
895             {
896             $return = { is_nullable => 0 }
897             }
898             | DROP not_null
899             {
900             $return = { is_nullable => 1 }
901             }
902              
903             not_null : /not/i /null/i
904              
905             not : /not/i
906              
907             add_column : ADD COLUMN(?)
908              
909             alter_table : ALTER TABLE ONLY(?)
910              
911             alter_sequence : ALTER SEQUENCE
912              
913             drop_column : DROP COLUMN(?)
914              
915             alter_column : ALTER COLUMN(?)
916              
917             rename_column : /rename/i COLUMN(?)
918              
919             restrict_or_cascade : /restrict/i |
920             /cascade/i
921              
922             # Handle functions that can be called
923             select : SELECT select_function ';'
924             { 1 }
925              
926             # Read the setval function but don't do anything with it because this parser
927             # isn't handling sequences
928             select_function : schema_qualification(?) /setval/i '(' VALUE /,/ VALUE /,/ /(true|false)/i ')'
929             { 1 }
930              
931             # Skipping all COPY commands
932             copy : COPY WORD /[^;]+/ ';' { 1 }
933             { 1 }
934              
935             # The "\." allows reading in from STDIN but this isn't needed for schema
936             # creation, so it is skipped.
937             readin_symbol : '\.'
938             {1}
939              
940             #
941             # End basically useless stuff. - ky
942             #
943              
944             create_table : CREATE TABLE
945              
946             create_index : CREATE /index/i
947              
948             default_val : DEFAULT DEFAULT_VALUE ( '::' data_type )(?)
949             {
950             my $val = $item[2];
951             $val =~ s/^\((\d+)\)\z/$1/; # for example (0)::smallint
952             $return = {
953             supertype => 'constraint',
954             type => 'default',
955             value => $val,
956             }
957             }
958             | /null/i
959             {
960             $return = {
961             supertype => 'constraint',
962             type => 'default',
963             value => 'NULL',
964             }
965             }
966              
967             DEFAULT_VALUE : VALUE
968             | /\w+\(.*\)/
969             | /\w+/
970             | /\(\d+\)/
971              
972             name_with_opt_paren : NAME parens_value_list(s?)
973             { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
974              
975             unique : /unique/i { 1 }
976              
977             key : /key/i | /index/i
978              
979             table_option : /inherits/i '(' NAME(s /,/) ')'
980             {
981             $return = { type => 'inherits', table_name => $item[3] }
982             }
983             |
984             /with(out)? oids/i
985             {
986             $return = { type => $item[1] =~ /out/i ? 'without_oids' : 'with_oids' }
987             }
988              
989             ADD : /add/i
990              
991             ALTER : /alter/i
992              
993             CREATE : /create/i
994              
995             ONLY : /only/i
996              
997             DEFAULT : /default/i
998              
999             DROP : /drop/i
1000              
1001             COLUMN : /column/i
1002              
1003             TABLE : /table/i
1004              
1005             VIEW : /view/i
1006              
1007             SCHEMA : /schema/i
1008              
1009             SEMICOLON : /\s*;\n?/
1010              
1011             SEQUENCE : /sequence/i
1012              
1013             SELECT : /select/i
1014              
1015             COPY : /copy/i
1016              
1017             INTEGER : /\d+/
1018              
1019             WORD : /\w+/
1020              
1021             DIGITS : /\d+/
1022              
1023             COMMA : ','
1024              
1025             SET : /set/i
1026              
1027             NAME : DQSTRING
1028             | /\w+/
1029              
1030             DQSTRING : '"' /((?:[^"]|"")+)/ '"'
1031             { ($return = $item[3]) =~ s/""/"/g; }
1032              
1033             SQSTRING : "'" /((?:[^']|'')*)/ "'"
1034             { ($return = $item[3]) =~ s/''/'/g }
1035              
1036             DOLLARSTRING : /\$[^\$]*\$/ /.*?(?=\Q$item[1]\E)/s "$item[1]"
1037             { $return = $item[3]; }
1038              
1039             VALUE : /[-+]?\d*\.?\d+(?:[eE]\d+)?/
1040             | SQSTRING
1041             | DOLLARSTRING
1042             | /null/i
1043             { 'NULL' }
1044              
1045             END_OF_GRAMMAR
1046              
1047             sub parse {
1048 4     4 0 44 my ( $translator, $data ) = @_;
1049              
1050             # Enable warnings within the Parse::RecDescent module.
1051 4 100       14 local $::RD_ERRORS = 1 unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
1052 4 100       39 local $::RD_WARN = 1 unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
1053 4 50       82 local $::RD_HINT = 1 unless defined $::RD_HINT; # Give out hints to help fix problems.
1054              
1055 4 50       76 local $::RD_TRACE = $translator->trace ? 1 : undef;
1056 4         47 local $DEBUG = $translator->debug;
1057              
1058 4         42 my $parser = ddl_parser_instance('PostgreSQL');
1059              
1060 4         2874594 my $result = $parser->startrule($data);
1061 4 50       1398645 die "Parse failed.\n" unless defined $result;
1062 4 50       15 warn Dumper($result) if $DEBUG;
1063              
1064 4         98 my $schema = $translator->schema;
1065             my @tables = sort {
1066 26   50     105 ( $result->{tables}{ $a }{'order'} || 0 ) <=> ( $result->{tables}{ $b }{'order'} || 0 )
      50        
1067 4         312 } keys %{ $result->{tables} };
  4         36  
1068              
1069 4         17 for my $table_name ( @tables ) {
1070 19         74 my $tdata = $result->{tables}{ $table_name };
1071             my $table = $schema->add_table(
1072             #schema => $tdata->{'schema_name'},
1073 19 50       118 name => $tdata->{'table_name'},
1074             ) or die "Couldn't create table '$table_name': " . $schema->error;
1075              
1076 19 100       411 $table->extra(temporary => 1) if $tdata->{'temporary'};
1077              
1078 19         394 $table->comments( $tdata->{'comments'} );
1079              
1080             my @fields = sort {
1081             $tdata->{'fields'}{ $a }{'order'}
1082             <=>
1083 171         334 $tdata->{'fields'}{ $b }{'order'}
1084 19         38 } keys %{ $tdata->{'fields'} };
  19         143  
1085              
1086 19         60 for my $fname ( @fields ) {
1087 86         199 my $fdata = $tdata->{'fields'}{ $fname };
1088 86 100       200 next if $fdata->{'drop'};
1089             my $field = $table->add_field(
1090             name => $fdata->{'name'},
1091             data_type => $fdata->{'data_type'},
1092             size => $fdata->{'size'},
1093             default_value => $fdata->{'default'},
1094             is_auto_increment => $fdata->{'is_auto_increment'},
1095             is_nullable => $fdata->{'is_nullable'},
1096 85 50       488 comments => $fdata->{'comments'},
1097             ) or die $table->error;
1098              
1099 85 100       1484 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
1100              
1101 85         130 for my $cdata ( @{ $fdata->{'constraints'} } ) {
  85         267  
1102 34 100       162 next unless $cdata->{'type'} eq 'foreign_key';
1103 1   50     3 $cdata->{'fields'} ||= [ $field->name ];
1104 1         2 push @{ $tdata->{'constraints'} }, $cdata;
  1         3  
1105             }
1106             }
1107              
1108 19 100       53 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
  19         108  
1109 16         34 my @options = ();
1110 16 100       60 push @options, { using => $idata->{'method'} } if $idata->{method};
1111 16 100       47 push @options, { where => $idata->{'where'} } if $idata->{where};
1112 16 100       43 push @options, { include => $idata->{'include'} } if $idata->{include};
1113             my $index = $table->add_index(
1114             name => $idata->{'name'},
1115             type => uc $idata->{'type'},
1116 16 50       94 fields => $idata->{'fields'},
1117             options => \@options
1118             ) or die $table->error . ' ' . $table->name;
1119             }
1120              
1121 19 100       62 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
  19         91  
1122             my $constraint = $table->add_constraint(
1123             name => $cdata->{'name'},
1124             type => $cdata->{'type'},
1125             fields => $cdata->{'fields'},
1126             reference_table => $cdata->{'reference_table'},
1127             reference_fields => $cdata->{'reference_fields'},
1128             match_type => $cdata->{'match_type'} || '',
1129             on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
1130             on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
1131             expression => $cdata->{'expression'},
1132             ) or die "Can't add constraint of type '" .
1133 72 50 100     784 $cdata->{'type'} . "' to table '" . $table->name .
      66        
      66        
1134             "': " . $table->error;
1135             }
1136             }
1137              
1138 4         13 for my $vinfo (@{$result->{views}}) {
  4         19  
1139 4         13 my $sql = $vinfo->{sql};
1140 4         35 $sql =~ s/\A\s+|\s+\z//g;
1141             my $view = $schema->add_view (
1142             name => $vinfo->{view_name},
1143             sql => $sql,
1144             fields => $vinfo->{fields},
1145 4         27 );
1146              
1147 4 100       42 $view->extra ( temporary => 1 ) if $vinfo->{is_temporary};
1148             }
1149              
1150 4         9 for my $trigger (@{ $result->{triggers} }) {
  4         14  
1151 7         38 $schema->add_trigger( %$trigger );
1152             }
1153              
1154 4         45 return 1;
1155             }
1156              
1157             1;
1158              
1159             # -------------------------------------------------------------------
1160             # Rescue the drowning and tie your shoestrings.
1161             # Henry David Thoreau
1162             # -------------------------------------------------------------------
1163              
1164             =pod
1165              
1166             =head1 AUTHORS
1167              
1168             Ken Y. Clark Ekclark@cpan.orgE,
1169             Allen Day Eallenday@ucla.eduE.
1170              
1171             =head1 SEE ALSO
1172              
1173             perl(1), Parse::RecDescent.
1174              
1175             =cut