File Coverage

blib/lib/SQL/Translator/Parser/PostgreSQL.pm
Criterion Covered Total %
statement 69 69 100.0
branch 32 40 80.0
condition 9 14 64.2
subroutine 6 6 100.0
pod 0 1 0.0
total 116 130 89.2


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 ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
39              
40             and table_constraint is:
41              
42             [ CONSTRAINT constraint_name ]
43             { UNIQUE ( column_name [, ... ] ) |
44             PRIMARY KEY ( column_name [, ... ] ) |
45             CHECK ( expression ) |
46             EXCLUDE [USING acc_method] (expression) [INCLUDE (column [, ...])] [WHERE (predicate)]
47             FOREIGN KEY ( column_name [, ... ] )
48             REFERENCES reftable [ ( refcolumn [, ... ] ) ]
49             [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
50             [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
51              
52             Index :
53             (http://www.postgresql.org/docs/current/sql-createindex.html)
54              
55             CREATE [ UNIQUE ] INDEX index_name ON table
56             [ USING acc_method ] ( column [ ops_name ] [, ...] )
57             [ INCLUDE ( column [, ...] ) ]
58             [ WHERE predicate ]
59             CREATE [ UNIQUE ] INDEX index_name ON table
60             [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
61             [ WHERE predicate ]
62              
63             Alter table:
64              
65             ALTER TABLE [ ONLY ] table [ * ]
66             ADD [ COLUMN ] column type [ column_constraint [ ... ] ]
67             ALTER TABLE [ ONLY ] table [ * ]
68             ALTER [ COLUMN ] column { SET DEFAULT value | DROP DEFAULT }
69             ALTER TABLE [ ONLY ] table [ * ]
70             ALTER [ COLUMN ] column SET STATISTICS integer
71             ALTER TABLE [ ONLY ] table [ * ]
72             RENAME [ COLUMN ] column TO newcolumn
73             ALTER TABLE table
74             RENAME TO new_table
75             ALTER TABLE table
76             ADD table_constraint_definition
77             ALTER TABLE [ ONLY ] table
78             DROP CONSTRAINT constraint { RESTRICT | CASCADE }
79             ALTER TABLE table
80             OWNER TO new_owner
81              
82             View :
83              
84             CREATE [ OR REPLACE ] VIEW view [ ( column name list ) ] AS SELECT query
85              
86             =cut
87              
88 3     3   2450 use strict;
  3         9  
  3         207  
89 3     3   18 use warnings;
  3         7  
  3         390  
90              
91             our $VERSION = '1.66';
92              
93             our $DEBUG;
94             $DEBUG = 0 unless defined $DEBUG;
95              
96 3     3   24 use Data::Dumper;
  3         7  
  3         248  
97 3     3   22 use SQL::Translator::Utils qw/ddl_parser_instance/;
  3         7  
  3         261  
98              
99 3     3   23 use base qw(Exporter);
  3         6  
  3         4774  
100             our @EXPORT_OK = qw(parse);
101              
102             our $GRAMMAR = <<'END_OF_GRAMMAR';
103              
104             { my ( %tables, @views, @triggers, $table_order, $field_order, @table_comments) }
105              
106             #
107             # The "eofile" rule makes the parser fail if any "statement" rule
108             # fails. Otherwise, the first successful match by a "statement"
109             # won't cause the failure needed to know that the parse, as a whole,
110             # failed. -ky
111             #
112             startrule : statement(s) eofile {
113             {
114             tables => \%tables,
115             views => \@views,
116             triggers => \@triggers,
117             }
118             }
119              
120             eofile : /^\Z/
121              
122             statement : create
123             | comment_on_table
124             | comment_on_column
125             | comment_on_other
126             | comment
127             | alter
128             | grant
129             | revoke
130             | drop
131             | insert
132             | connect
133             | update
134             | set
135             | select
136             | copy
137             | readin_symbol
138             | commit
139             |
140              
141             commit : /commit/i ';'
142              
143             connect : /^\s*\\connect.*\n/
144              
145             set : /set/i /[^;]*/ ';'
146              
147             revoke : /revoke/i WORD(s /,/) /on/i TABLE(?) table_id /from/i NAME(s /,/) ';'
148             {
149             my $table_info = $item{'table_id'};
150             my $schema_name = $table_info->{'schema_name'};
151             my $table_name = $table_info->{'table_name'};
152             push @{ $tables{ $table_name }{'permissions'} }, {
153             type => 'revoke',
154             actions => $item[2],
155             users => $item[7],
156             }
157             }
158              
159             revoke : /revoke/i WORD(s /,/) /on/i SCHEMA(?) schema_name /from/i NAME(s /,/) ';'
160             { 1 }
161              
162             grant : /grant/i WORD(s /,/) /on/i TABLE(?) table_id /to/i NAME(s /,/) ';'
163             {
164             my $table_info = $item{'table_id'};
165             my $schema_name = $table_info->{'schema_name'};
166             my $table_name = $table_info->{'table_name'};
167             push @{ $tables{ $table_name }{'permissions'} }, {
168             type => 'grant',
169             actions => $item[2],
170             users => $item[7],
171             }
172             }
173              
174             grant : /grant/i WORD(s /,/) /on/i SCHEMA(?) schema_name /to/i NAME(s /,/) ';'
175             { 1 }
176              
177             drop : /drop/i /[^;]*/ ';'
178              
179             string :
180             /'(\.|''|[^\\'])*'/
181              
182             nonstring : /[^;\'"]+/
183              
184             statement_body : string | nonstring
185              
186             insert : /insert/i statement_body(s?) ';'
187              
188             update : /update/i statement_body(s?) ';'
189              
190             #
191             # Create table.
192             #
193             create : CREATE temporary(?) TABLE table_id '(' create_definition(s? /,/) ')' table_option(s?) ';'
194             {
195             my $table_info = $item{'table_id'};
196             my $schema_name = $table_info->{'schema_name'};
197             my $table_name = $table_info->{'table_name'};
198             $tables{ $table_name }{'order'} = ++$table_order;
199             $tables{ $table_name }{'schema_name'} = $schema_name;
200             $tables{ $table_name }{'table_name'} = $table_name;
201              
202             $tables{ $table_name }{'temporary'} = $item[2][0];
203              
204             if ( @table_comments ) {
205             $tables{ $table_name }{'comments'} = [ @table_comments ];
206             @table_comments = ();
207             }
208              
209             my @constraints;
210             for my $definition ( @{ $item[6] } ) {
211             if ( $definition->{'supertype'} eq 'field' ) {
212             my $field_name = $definition->{'name'};
213             $tables{ $table_name }{'fields'}{ $field_name } =
214             { %$definition, order => $field_order++ };
215              
216             for my $constraint ( @{ $definition->{'constraints'} || [] } ) {
217             $constraint->{'fields'} = [ $field_name ];
218             push @{ $tables{ $table_name }{'constraints'} },
219             $constraint;
220             }
221             }
222             elsif ( $definition->{'supertype'} eq 'constraint' ) {
223             push @{ $tables{ $table_name }{'constraints'} }, $definition;
224             }
225             elsif ( $definition->{'supertype'} eq 'index' ) {
226             push @{ $tables{ $table_name }{'indices'} }, $definition;
227             }
228             }
229              
230             for my $option ( @{ $item[8] } ) {
231             $tables{ $table_name }{'table_options(s?)'}{ $option->{'type'} } =
232             $option;
233             }
234              
235             1;
236             }
237              
238             create : CREATE unique(?) /(index|key)/i index_name /on/i table_id using_method(?) '(' field_name(s /,/) ')' include_covering(?) where_predicate(?) ';'
239             {
240             my $table_info = $item{'table_id'};
241             my $schema_name = $table_info->{'schema_name'};
242             my $table_name = $table_info->{'table_name'};
243             push @{ $tables{ $table_name }{'indices'} },
244             {
245             name => $item{'index_name'},
246             supertype => $item{'unique'}[0] ? 'constraint' : 'index',
247             type => $item{'unique'}[0] ? 'unique' : 'normal',
248             fields => $item[9],
249             method => $item{'using_method(?)'}[0],
250             where => $item{'where_predicate(?)'}[0],
251             include => $item{'include_covering(?)'}[0]
252             }
253             ;
254             }
255              
256             create : CREATE or_replace(?) temporary(?) VIEW view_id view_fields(?) /AS/i view_target ';'
257             {
258             push @views, {
259             schema_name => $item{view_id}{schema_name},
260             view_name => $item{view_id}{view_name},
261             sql => $item{view_target},
262             fields => $item[6],
263             is_temporary => $item[3][0],
264             }
265             }
266              
267             create: CREATE /MATERIALIZED VIEW/i if_not_exists(?) view_id view_fields(?) /AS/i view_target ';'
268             {
269             push @views, {
270             schema_name => $item{view_id}{schema_name},
271             view_name => $item{view_id}{view_name},
272             sql => $item{view_target},
273             fields => $item[5],
274             extra => { materialized => 1 }
275             }
276             }
277              
278             if_not_exists : /IF NOT EXISTS/i
279              
280             trigger_name : NAME
281              
282             trigger_scope : /FOR/i /EACH/i /(ROW|STATEMENT)/i { $return = lc $1 }
283              
284             before_or_after : /(before|after)/i { $return = lc $1 }
285              
286             trigger_action : /.+/
287              
288             database_event : /insert|update|delete/i
289             database_events : database_event(s /OR/)
290              
291             create : CREATE /TRIGGER/i trigger_name before_or_after database_events /ON/i table_id trigger_scope(?) trigger_action
292             {
293             # Hack to pass roundtrip tests which have trigger statements terminated by double semicolon
294             # and expect the returned data to have the same
295             my $action = $item{trigger_action};
296             $action =~ s/;$//;
297              
298             push @triggers, {
299             name => $item{trigger_name},
300             perform_action_when => $item{before_or_after},
301             database_events => $item{database_events},
302             on_table => $item{table_id}{table_name},
303             scope => $item{'trigger_scope(?)'}[0],
304             action => $action,
305             }
306             }
307              
308             #
309             # Create anything else (e.g., domain, etc.)
310             #
311             create : CREATE WORD /[^;]+/ ';'
312             { @table_comments = (); }
313              
314             using_method : /using/i WORD { $item[2] }
315              
316             where_predicate : /where/i /[^;]+/
317              
318             where_paren_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             my $expr_constraint = $type eq 'check' || $type eq 'exclude';
683              
684             $return = {
685             name => $item[2][0] || '',
686             supertype => 'constraint',
687             type => $type,
688             fields => $expr_constraint ? [] : $fields,
689             expression => $expr_constraint ? $expression : '',
690             deferrable => $item{'deferrable'},
691             deferred => $item{'deferred'},
692             on_delete => $desc->{'on_delete'} || $desc->{'on_delete_do'},
693             on_update => $desc->{'on_update'} || $desc->{'on_update_do'},
694             comments => [ @comments ],
695             %{$desc}{qw/include using where reference_table reference_fields match_type/}
696             }
697             }
698              
699             table_constraint_type : /primary key/i '(' NAME(s /,/) ')' include_covering(?)
700             {
701             $return = {
702             type => 'primary_key',
703             fields => $item[3],
704             include => $item{'include_convering(?)'}[0],
705             }
706             }
707             |
708             /unique/i '(' NAME(s /,/) ')' include_covering(?)
709             {
710             $return = {
711             type => 'unique',
712             fields => $item[3],
713             include => $item{'include_convering(?)'}[0],
714             }
715             }
716             |
717             /check/i '(' /[^)]+/ ')'
718             {
719             $return = {
720             type => 'check',
721             expression => $item[3],
722             }
723             }
724             |
725             /exclude/i using_method(?) '(' /[^)]+/ ')' include_covering(?) where_paren_predicate(?) {
726             $return = {
727             type => 'exclude',
728             expression => $item{__PATTERN2__},
729             using => $item{'using_method(?)'}[0],
730             include => $item{'include_convering(?)'}[0],
731             where => $item{'where_paren_predicate(?)'}[0],
732             }
733             }
734             |
735             /foreign key/i '(' NAME(s /,/) ')' /references/i table_id parens_word_list(?) match_type(?) key_action(s?)
736             {
737             my ( $on_delete, $on_update );
738             for my $action ( @{ $item[9] || [] } ) {
739             $on_delete = $action->{'action'} if $action->{'type'} eq 'delete';
740             $on_update = $action->{'action'} if $action->{'type'} eq 'update';
741             }
742              
743             $return = {
744             supertype => 'constraint',
745             type => 'foreign_key',
746             fields => $item[3],
747             reference_table => $item[6]->{'table_name'},
748             reference_fields => $item[7][0],
749             match_type => $item[8][0],
750             on_delete => $on_delete || '',
751             on_update => $on_update || '',
752             }
753             }
754              
755             deferrable : not(?) /deferrable/i
756             {
757             $return = ( $item[1] =~ /not/i ) ? 0 : 1;
758             }
759              
760             deferred : /initially/i /(deferred|immediate)/i { $item[2] }
761              
762             match_type : /match/i /partial|full|simple/i { $item[2] }
763              
764             key_action : key_delete
765             |
766             key_update
767              
768             key_delete : /on delete/i key_mutation
769             {
770             $return = {
771             type => 'delete',
772             action => $item[2],
773             };
774             }
775              
776             key_update : /on update/i key_mutation
777             {
778             $return = {
779             type => 'update',
780             action => $item[2],
781             };
782             }
783              
784             key_mutation : /no action/i { $return = 'no_action' }
785             |
786             /restrict/i { $return = 'restrict' }
787             |
788             /cascade/i { $return = 'cascade' }
789             |
790             /set null/i { $return = 'set null' }
791             |
792             /set default/i { $return = 'set default' }
793              
794             alter : alter_table table_id add_column field ';'
795             {
796             my $field_def = $item[4];
797             $tables{ $item[2]->{'table_name'} }{'fields'}{ $field_def->{'name'} } = {
798             %$field_def, order => $field_order++
799             };
800             1;
801             }
802              
803             alter : alter_table table_id ADD table_constraint ';'
804             {
805             my $table_name = $item[2]->{'table_name'};
806             my $constraint = $item[4];
807             push @{ $tables{ $table_name }{'constraints'} }, $constraint;
808             1;
809             }
810              
811             alter : alter_table table_id drop_column NAME restrict_or_cascade(?) ';'
812             {
813             $tables{ $item[2]->{'table_name'} }{'fields'}{ $item[4] }{'drop'} = 1;
814             1;
815             }
816              
817             alter : alter_table table_id alter_column NAME alter_default_val ';'
818             {
819             $tables{ $item[2]->{'table_name'} }{'fields'}{ $item[4] }{'default'} =
820             $item[5]->{'value'};
821             1;
822             }
823              
824             #
825             # These will just parse for now but won't affect the structure. - ky
826             #
827             alter : alter_table table_id /rename/i /to/i NAME ';'
828             { 1 }
829              
830             alter : alter_table table_id alter_column NAME SET /statistics/i INTEGER ';'
831             { 1 }
832              
833             alter : alter_table table_id alter_column NAME SET /storage/i storage_type ';'
834             { 1 }
835              
836             alter : alter_table table_id rename_column NAME /to/i NAME ';'
837             { 1 }
838              
839             alter : alter_table table_id DROP /constraint/i NAME restrict_or_cascade ';'
840             { 1 }
841              
842             alter : alter_table table_id /owner/i /to/i NAME ';'
843             { 1 }
844              
845             alter : alter_sequence NAME /owned/i /by/i column_name ';'
846             { 1 }
847              
848             storage_type : /(plain|external|extended|main)/i
849              
850             temporary : /temp(orary)?\b/i
851             {
852             1;
853             }
854              
855             or_replace : /or replace/i
856              
857             alter_default_val : SET default_val
858             {
859             $return = { value => $item[2]->{'value'} }
860             }
861             | DROP DEFAULT
862             {
863             $return = { value => undef }
864             }
865              
866             #
867             # This is a little tricky to get right, at least WRT to making the
868             # tests pass. The problem is that the constraints are stored just as
869             # a list (no name access), and the tests expect the constraints in a
870             # particular order. I'm going to leave the rule but disable the code
871             # for now. - ky
872             #
873             alter : alter_table table_id alter_column NAME alter_nullable ';'
874             {
875             # my $table_name = $item[2]->{'table_name'};
876             # my $field_name = $item[4];
877             # my $is_nullable = $item[5]->{'is_nullable'};
878             #
879             # $tables{ $table_name }{'fields'}{ $field_name }{'is_nullable'} =
880             # $is_nullable;
881             #
882             # if ( $is_nullable ) {
883             # 1;
884             # push @{ $tables{ $table_name }{'constraints'} }, {
885             # type => 'not_null',
886             # fields => [ $field_name ],
887             # };
888             # }
889             # else {
890             # for my $i (
891             # 0 .. $#{ $tables{ $table_name }{'constraints'} || [] }
892             # ) {
893             # my $c = $tables{ $table_name }{'constraints'}[ $i ] or next;
894             # my $fields = join( '', @{ $c->{'fields'} || [] } ) or next;
895             # if ( $c->{'type'} eq 'not_null' && $fields eq $field_name ) {
896             # delete $tables{ $table_name }{'constraints'}[ $i ];
897             # last;
898             # }
899             # }
900             # }
901              
902             1;
903             }
904              
905             alter_nullable : SET not_null
906             {
907             $return = { is_nullable => 0 }
908             }
909             | DROP not_null
910             {
911             $return = { is_nullable => 1 }
912             }
913              
914             not_null : /not/i /null/i
915              
916             not : /not/i
917              
918             add_column : ADD COLUMN(?)
919              
920             alter_table : ALTER TABLE ONLY(?)
921              
922             alter_sequence : ALTER SEQUENCE
923              
924             drop_column : DROP COLUMN(?)
925              
926             alter_column : ALTER COLUMN(?)
927              
928             rename_column : /rename/i COLUMN(?)
929              
930             restrict_or_cascade : /restrict/i |
931             /cascade/i
932              
933             # Handle functions that can be called
934             select : SELECT select_function ';'
935             { 1 }
936              
937             # Read the setval function but don't do anything with it because this parser
938             # isn't handling sequences
939             select_function : schema_qualification(?) /setval/i '(' VALUE /,/ VALUE /,/ /(true|false)/i ')'
940             { 1 }
941              
942             # Skipping all COPY commands
943             copy : COPY WORD /[^;]+/ ';' { 1 }
944             { 1 }
945              
946             # The "\." allows reading in from STDIN but this isn't needed for schema
947             # creation, so it is skipped.
948             readin_symbol : '\.'
949             {1}
950              
951             #
952             # End basically useless stuff. - ky
953             #
954              
955             create_table : CREATE TABLE
956              
957             create_index : CREATE /index/i
958              
959             default_val : DEFAULT DEFAULT_VALUE ( '::' data_type )(?)
960             {
961             my $val = $item[2];
962             $val =~ s/^\((\d+)\)\z/$1/; # for example (0)::smallint
963             $return = {
964             supertype => 'constraint',
965             type => 'default',
966             value => $val,
967             }
968             }
969             | /null/i
970             {
971             $return = {
972             supertype => 'constraint',
973             type => 'default',
974             value => 'NULL',
975             }
976             }
977              
978             DEFAULT_VALUE : VALUE
979             | /\w+\(.*\)/
980             | /\w+/
981             | /\(\d+\)/
982              
983             name_with_opt_paren : NAME parens_value_list(s?)
984             { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
985              
986             unique : /unique/i { 1 }
987              
988             key : /key/i | /index/i
989              
990             table_option : /inherits/i '(' NAME(s /,/) ')'
991             {
992             $return = { type => 'inherits', table_name => $item[3] }
993             }
994             |
995             /with(out)? oids/i
996             {
997             $return = { type => $item[1] =~ /out/i ? 'without_oids' : 'with_oids' }
998             }
999              
1000             ADD : /add/i
1001              
1002             ALTER : /alter/i
1003              
1004             CREATE : /create/i
1005              
1006             ONLY : /only/i
1007              
1008             DEFAULT : /default/i
1009              
1010             DROP : /drop/i
1011              
1012             COLUMN : /column/i
1013              
1014             TABLE : /table/i
1015              
1016             VIEW : /view/i
1017              
1018             SCHEMA : /schema/i
1019              
1020             SEMICOLON : /\s*;\n?/
1021              
1022             SEQUENCE : /sequence/i
1023              
1024             SELECT : /select/i
1025              
1026             COPY : /copy/i
1027              
1028             INTEGER : /\d+/
1029              
1030             WORD : /\w+/
1031              
1032             DIGITS : /\d+/
1033              
1034             COMMA : ','
1035              
1036             SET : /set/i
1037              
1038             NAME : DQSTRING
1039             | /\w+/
1040              
1041             DQSTRING : '"' /((?:[^"]|"")+)/ '"'
1042             { ($return = $item[3]) =~ s/""/"/g; }
1043              
1044             SQSTRING : "'" /((?:[^']|'')*)/ "'"
1045             { ($return = $item[3]) =~ s/''/'/g }
1046              
1047             DOLLARSTRING : /\$[^\$]*\$/ /.*?(?=\Q$item[1]\E)/s "$item[1]"
1048             { $return = $item[3]; }
1049              
1050             VALUE : /[-+]?\d*\.?\d+(?:[eE]\d+)?/
1051             | SQSTRING
1052             | DOLLARSTRING
1053             | /null/i
1054             { 'NULL' }
1055              
1056             END_OF_GRAMMAR
1057              
1058             sub parse {
1059 4     4 0 66 my ($translator, $data) = @_;
1060              
1061             # Enable warnings within the Parse::RecDescent module.
1062 4 100       44 local $::RD_ERRORS = 1
1063             unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
1064 4 100       33 local $::RD_WARN = 1
1065             unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
1066 4 50       19 local $::RD_HINT = 1
1067             unless defined $::RD_HINT; # Give out hints to help fix problems.
1068              
1069 4 50       166 local $::RD_TRACE = $translator->trace ? 1 : undef;
1070 4         58 local $DEBUG = $translator->debug;
1071              
1072 4         54 my $parser = ddl_parser_instance('PostgreSQL');
1073              
1074 4         4475640 my $result = $parser->startrule($data);
1075 4 50       1927781 die "Parse failed.\n" unless defined $result;
1076 4 50       21 warn Dumper($result) if $DEBUG;
1077              
1078 4         275 my $schema = $translator->schema;
1079 26   50     127 my @tables = sort { ($result->{tables}{$a}{'order'} || 0) <=> ($result->{tables}{$b}{'order'} || 0) }
      50        
1080 4         301 keys %{ $result->{tables} };
  4         65  
1081              
1082 4         14 for my $table_name (@tables) {
1083 19         114 my $tdata = $result->{tables}{$table_name};
1084             my $table = $schema->add_table(
1085              
1086             #schema => $tdata->{'schema_name'},
1087 19 50       202 name => $tdata->{'table_name'},
1088             ) or die "Couldn't create table '$table_name': " . $schema->error;
1089              
1090 19 100       485 $table->extra(temporary => 1) if $tdata->{'temporary'};
1091              
1092 19         501 $table->comments($tdata->{'comments'});
1093              
1094             my @fields
1095 19         54 = sort { $tdata->{'fields'}{$a}{'order'} <=> $tdata->{'fields'}{$b}{'order'} } keys %{ $tdata->{'fields'} };
  164         394  
  19         171  
1096              
1097 19         63 for my $fname (@fields) {
1098 86         293 my $fdata = $tdata->{'fields'}{$fname};
1099 86 100       271 next if $fdata->{'drop'};
1100             my $field = $table->add_field(
1101             name => $fdata->{'name'},
1102             data_type => $fdata->{'data_type'},
1103             size => $fdata->{'size'},
1104             default_value => $fdata->{'default'},
1105             is_auto_increment => $fdata->{'is_auto_increment'},
1106             is_nullable => $fdata->{'is_nullable'},
1107 85 50       720 comments => $fdata->{'comments'},
1108             ) or die $table->error;
1109              
1110 85 100       1958 $table->primary_key($field->name) if $fdata->{'is_primary_key'};
1111              
1112 85         146 for my $cdata (@{ $fdata->{'constraints'} }) {
  85         399  
1113 34 100       231 next unless $cdata->{'type'} eq 'foreign_key';
1114 1   50     8 $cdata->{'fields'} ||= [ $field->name ];
1115 1         3 push @{ $tdata->{'constraints'} }, $cdata;
  1         6  
1116             }
1117             }
1118              
1119 19 100       50 for my $idata (@{ $tdata->{'indices'} || [] }) {
  19         175  
1120 16         42 my @options = ();
1121 16 100       101 push @options, { using => $idata->{'method'} } if $idata->{method};
1122 16 100       63 push @options, { where => $idata->{'where'} } if $idata->{where};
1123             push @options, { include => $idata->{'include'} }
1124 16 100       62 if $idata->{include};
1125             my $index = $table->add_index(
1126             name => $idata->{'name'},
1127             type => uc $idata->{'type'},
1128 16 50       139 fields => $idata->{'fields'},
1129             options => \@options
1130             ) or die $table->error . ' ' . $table->name;
1131             }
1132              
1133 19 100       52 for my $cdata (@{ $tdata->{'constraints'} || [] }) {
  19         117  
1134             my $options = [
1135              
1136             # load this up with the extras
1137 73         441 map +{ %$cdata{$_} }, grep $cdata->{$_},
1138             qw/include using where/
1139             ];
1140             my $constraint = $table->add_constraint(
1141             name => $cdata->{'name'},
1142             type => $cdata->{'type'},
1143             fields => $cdata->{'fields'},
1144             reference_table => $cdata->{'reference_table'},
1145             reference_fields => $cdata->{'reference_fields'},
1146             match_type => $cdata->{'match_type'} || '',
1147             on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
1148             on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
1149             expression => $cdata->{'expression'},
1150             options => $options
1151             )
1152             or die "Can't add constraint of type '"
1153 73 50 100     1166 . $cdata->{'type'}
      66        
      66        
1154             . "' to table '"
1155             . $table->name . "': "
1156             . $table->error;
1157             }
1158             }
1159              
1160 4         14 for my $vinfo (@{ $result->{views} }) {
  4         23  
1161 4         18 my $sql = $vinfo->{sql};
1162 4         56 $sql =~ s/\A\s+|\s+\z//g;
1163             my $view = $schema->add_view(
1164             name => $vinfo->{view_name},
1165             sql => $sql,
1166             fields => $vinfo->{fields},
1167 4         35 );
1168              
1169 4 100       56 $view->extra(temporary => 1) if $vinfo->{is_temporary};
1170             }
1171              
1172 4         13 for my $trigger (@{ $result->{triggers} }) {
  4         23  
1173 7         55 $schema->add_trigger(%$trigger);
1174             }
1175              
1176 4         64 return 1;
1177             }
1178              
1179             1;
1180              
1181             # -------------------------------------------------------------------
1182             # Rescue the drowning and tie your shoestrings.
1183             # Henry David Thoreau
1184             # -------------------------------------------------------------------
1185              
1186             =pod
1187              
1188             =head1 AUTHORS
1189              
1190             Ken Y. Clark Ekclark@cpan.orgE,
1191             Allen Day Eallenday@ucla.eduE.
1192              
1193             =head1 SEE ALSO
1194              
1195             perl(1), Parse::RecDescent.
1196              
1197             =cut