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