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