Mssql does not understand ON DELETE RESTRICT
[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 ];
4ab3763d 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
124my $parser; # should we do this? There's no programmic way to
125 # change the grammar, so I think this is safe.
126
127$GRAMMAR = q!
128
330e4686 129{ my ( %tables, @views, $table_order, $field_order, @table_comments) }
4422e22a 130
629b76f9 131#
132# The "eofile" rule makes the parser fail if any "statement" rule
133# fails. Otherwise, the first successful match by a "statement"
134# won't cause the failure needed to know that the parse, as a whole,
135# failed. -ky
136#
330e4686 137startrule : statement(s) eofile { { tables => \%tables, views => \@views } }
4422e22a 138
0efb6e1b 139eofile : /^\Z/
e4a9818d 140
0efb6e1b 141
142statement : create
ac397a49 143 | comment_on_table
144 | comment_on_column
e4a9818d 145 | comment_on_other
0efb6e1b 146 | comment
0012a163 147 | alter
0efb6e1b 148 | grant
149 | revoke
4422e22a 150 | drop
4d2da1f7 151 | insert
0efb6e1b 152 | connect
4d2da1f7 153 | update
0012a163 154 | set
34338cb2 155 | select
156 | copy
157 | readin_symbol
4422e22a 158 | <error>
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 |
b0f2faf8 616 /(timestamptz|timestamp)(?:\(\d\))?( with(out)? time zone)?/i
3022f45b 617 {
618 $return = { type => 'timestamp' };
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'},
666 match_type => $desc->{'match_type'}[0],
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
0efb6e1b 724match_type : /match full/i { 'match_full' }
725 |
726 /match partial/i { 'match_partial' }
727
3022f45b 728key_action : key_delete
729 |
730 key_update
0efb6e1b 731
3022f45b 732key_delete : /on delete/i key_mutation
733 {
8c12c406 734 $return = {
3022f45b 735 type => 'delete',
736 action => $item[2],
737 };
738 }
739
740key_update : /on update/i key_mutation
741 {
8c12c406 742 $return = {
3022f45b 743 type => 'update',
744 action => $item[2],
745 };
746 }
747
748key_mutation : /no action/i { $return = 'no_action' }
749 |
750 /restrict/i { $return = 'restrict' }
751 |
752 /cascade/i { $return = 'cascade' }
753 |
38a6a4f9 754 /set null/i { $return = 'set null' }
3022f45b 755 |
38a6a4f9 756 /set default/i { $return = 'set default' }
0efb6e1b 757
330e4686 758alter : alter_table table_id add_column field ';'
00ef67ea 759 {
760 my $field_def = $item[4];
a20abbda 761 $tables{ $item[2]->{'table_name'} }{'fields'}{ $field_def->{'name'} } = {
00ef67ea 762 %$field_def, order => $field_order++
763 };
764 1;
765 }
766
330e4686 767alter : alter_table table_id ADD table_constraint ';'
0012a163 768 {
a20abbda 769 my $table_name = $item[2]->{'table_name'};
0012a163 770 my $constraint = $item[4];
0012a163 771 push @{ $tables{ $table_name }{'constraints'} }, $constraint;
00ef67ea 772 1;
0012a163 773 }
774
330e4686 775alter : alter_table table_id drop_column NAME restrict_or_cascade(?) ';'
00ef67ea 776 {
a20abbda 777 $tables{ $item[2]->{'table_name'} }{'fields'}{ $item[4] }{'drop'} = 1;
00ef67ea 778 1;
779 }
0012a163 780
330e4686 781alter : alter_table table_id alter_column NAME alter_default_val ';'
00ef67ea 782 {
a20abbda 783 $tables{ $item[2]->{'table_name'} }{'fields'}{ $item[4] }{'default'} =
00ef67ea 784 $item[5]->{'value'};
785 1;
786 }
787
788#
789# These will just parse for now but won't affect the structure. - ky
790#
330e4686 791alter : alter_table table_id /rename/i /to/i NAME ';'
00ef67ea 792 { 1 }
793
330e4686 794alter : alter_table table_id alter_column NAME SET /statistics/i INTEGER ';'
00ef67ea 795 { 1 }
796
330e4686 797alter : alter_table table_id alter_column NAME SET /storage/i storage_type ';'
00ef67ea 798 { 1 }
799
330e4686 800alter : alter_table table_id rename_column NAME /to/i NAME ';'
00ef67ea 801 { 1 }
802
330e4686 803alter : alter_table table_id DROP /constraint/i NAME restrict_or_cascade ';'
00ef67ea 804 { 1 }
805
330e4686 806alter : alter_table table_id /owner/i /to/i NAME ';'
00ef67ea 807 { 1 }
808
6f28e7ac 809alter : alter_sequence NAME /owned/i /by/i column_name ';'
810 { 1 }
811
00ef67ea 812storage_type : /(plain|external|extended|main)/i
813
330e4686 814temporary : /temp(orary)?\\b/i
815 {
816 1;
817 }
3e98f7d9 818
330e4686 819or_replace : /or replace/i
3e98f7d9 820
00ef67ea 821alter_default_val : SET default_val
822 {
823 $return = { value => $item[2]->{'value'} }
824 }
825 | DROP DEFAULT
826 {
827 $return = { value => undef }
828 }
829
830#
831# This is a little tricky to get right, at least WRT to making the
832# tests pass. The problem is that the constraints are stored just as
833# a list (no name access), and the tests expect the constraints in a
834# particular order. I'm going to leave the rule but disable the code
835# for now. - ky
836#
330e4686 837alter : alter_table table_id alter_column NAME alter_nullable ';'
00ef67ea 838 {
a20abbda 839# my $table_name = $item[2]->{'table_name'};
00ef67ea 840# my $field_name = $item[4];
841# my $is_nullable = $item[5]->{'is_nullable'};
842#
843# $tables{ $table_name }{'fields'}{ $field_name }{'is_nullable'} =
844# $is_nullable;
845#
846# if ( $is_nullable ) {
847# 1;
848# push @{ $tables{ $table_name }{'constraints'} }, {
849# type => 'not_null',
850# fields => [ $field_name ],
851# };
852# }
853# else {
854# for my $i (
855# 0 .. $#{ $tables{ $table_name }{'constraints'} || [] }
856# ) {
857# my $c = $tables{ $table_name }{'constraints'}[ $i ] or next;
858# my $fields = join( '', @{ $c->{'fields'} || [] } ) or next;
859# if ( $c->{'type'} eq 'not_null' && $fields eq $field_name ) {
860# delete $tables{ $table_name }{'constraints'}[ $i ];
861# last;
862# }
863# }
864# }
865
866 1;
867 }
868
869alter_nullable : SET not_null
870 {
871 $return = { is_nullable => 0 }
872 }
873 | DROP not_null
874 {
875 $return = { is_nullable => 1 }
876 }
877
878not_null : /not/i /null/i
879
99f3fab6 880not : /not/i
881
00ef67ea 882add_column : ADD COLUMN(?)
883
884alter_table : ALTER TABLE ONLY(?)
885
6f28e7ac 886alter_sequence : ALTER SEQUENCE
887
00ef67ea 888drop_column : DROP COLUMN(?)
889
890alter_column : ALTER COLUMN(?)
891
892rename_column : /rename/i COLUMN(?)
893
894restrict_or_cascade : /restrict/i |
895 /cascade/i
896
34338cb2 897# Handle functions that can be called
898select : SELECT select_function ';'
899 { 1 }
900
901# Read the setval function but don't do anything with it because this parser
902# isn't handling sequences
903select_function : schema_qualification(?) /setval/i '(' VALUE /,/ VALUE /,/ /(true|false)/i ')'
904 { 1 }
905
906# Skipping all COPY commands
907copy : COPY WORD /[^;]+/ ';' { 1 }
908 { 1 }
909
910# The "\." allows reading in from STDIN but this isn't needed for schema
911# creation, so it is skipped.
912readin_symbol : '\.'
913 {1}
914
00ef67ea 915#
916# End basically useless stuff. - ky
917#
0012a163 918
211e2e90 919create_table : CREATE TABLE
0efb6e1b 920
211e2e90 921create_index : CREATE /index/i
4422e22a 922
37e74f25 923default_val : DEFAULT /(\d+|'[^']*'|\w+\(.*\))|\w+/
4422e22a 924 {
f04713db 925 my $val = defined $item[2] ? $item[2] : '';
926 $val =~ s/^'|'$//g;
0efb6e1b 927 $return = {
82968eb9 928 supertype => 'constraint',
929 type => 'default',
0efb6e1b 930 value => $val,
931 }
4422e22a 932 }
f27085c9 933 | /null/i
934 {
935 $return = {
936 supertype => 'constraint',
937 type => 'default',
938 value => 'NULL',
939 }
940 }
4422e22a 941
4422e22a 942name_with_opt_paren : NAME parens_value_list(s?)
0efb6e1b 943 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
4422e22a 944
945unique : /unique/i { 1 }
946
947key : /key/i | /index/i
948
0efb6e1b 949table_option : /inherits/i '(' name_with_opt_quotes(s /,/) ')'
4422e22a 950 {
0efb6e1b 951 $return = { type => 'inherits', table_name => $item[3] }
952 }
953 |
954 /with(out)? oids/i
955 {
956 $return = { type => $item[1] =~ /out/i ? 'without_oids' : 'with_oids' }
4422e22a 957 }
958
00ef67ea 959ADD : /add/i
960
961ALTER : /alter/i
962
211e2e90 963CREATE : /create/i
964
00ef67ea 965ONLY : /only/i
966
967DEFAULT : /default/i
968
969DROP : /drop/i
970
971COLUMN : /column/i
972
0d51cd9e 973TABLE : /table/i
974
330e4686 975VIEW : /view/i
976
34338cb2 977SCHEMA : /schema/i
978
0efb6e1b 979SEMICOLON : /\s*;\n?/
980
6f28e7ac 981SEQUENCE : /sequence/i
982
34338cb2 983SELECT : /select/i
984
985COPY : /copy/i
986
00ef67ea 987INTEGER : /\d+/
988
4422e22a 989WORD : /\w+/
990
991DIGITS : /\d+/
992
993COMMA : ','
994
00ef67ea 995SET : /set/i
996
4422e22a 997NAME : "`" /\w+/ "`"
998 { $item[2] }
999 | /\w+/
1000 { $item[1] }
0012a163 1001 | /[\$\w]+/
1002 { $item[1] }
4422e22a 1003
1004VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
1005 { $item[1] }
1006 | /'.*?'/ # XXX doesn't handle embedded quotes
1007 { $item[1] }
f04713db 1008 | /null/i
4422e22a 1009 { 'NULL' }
1010
1011!;
1012
1013# -------------------------------------------------------------------
1014sub parse {
1015 my ( $translator, $data ) = @_;
1016 $parser ||= Parse::RecDescent->new($GRAMMAR);
1017
1018 $::RD_TRACE = $translator->trace ? 1 : undef;
1019 $DEBUG = $translator->debug;
1020
1021 unless (defined $parser) {
1022 return $translator->error("Error instantiating Parse::RecDescent ".
1023 "instance: Bad grammer");
1024 }
1025
1026 my $result = $parser->startrule($data);
1027 die "Parse failed.\n" unless defined $result;
1028 warn Dumper($result) if $DEBUG;
82968eb9 1029
1030 my $schema = $translator->schema;
1031 my @tables = sort {
330e4686 1032 ( $result->{tables}{ $a }{'order'} || 0 ) <=> ( $result->{tables}{ $b }{'order'} || 0 )
1033 } keys %{ $result->{tables} };
82968eb9 1034
1035 for my $table_name ( @tables ) {
330e4686 1036 my $tdata = $result->{tables}{ $table_name };
82968eb9 1037 my $table = $schema->add_table(
a20abbda 1038 #schema => $tdata->{'schema_name'},
1039 name => $tdata->{'table_name'},
d7fcc1d6 1040 ) or die "Couldn't create table '$table_name': " . $schema->error;
82968eb9 1041
3e98f7d9 1042 $table->extra(temporary => 1) if $tdata->{'temporary'};
1043
a82fa2cb 1044 $table->comments( $tdata->{'comments'} );
1045
82968eb9 1046 my @fields = sort {
429f639c 1047 $tdata->{'fields'}{ $a }{'order'}
82968eb9 1048 <=>
429f639c 1049 $tdata->{'fields'}{ $b }{'order'}
82968eb9 1050 } keys %{ $tdata->{'fields'} };
1051
1052 for my $fname ( @fields ) {
1053 my $fdata = $tdata->{'fields'}{ $fname };
00ef67ea 1054 next if $fdata->{'drop'};
82968eb9 1055 my $field = $table->add_field(
1056 name => $fdata->{'name'},
1057 data_type => $fdata->{'data_type'},
1058 size => $fdata->{'size'},
1059 default_value => $fdata->{'default'},
7eac5e12 1060 is_auto_increment => $fdata->{'is_auto_increment'},
00ef67ea 1061 is_nullable => $fdata->{'is_nullable'},
a82fa2cb 1062 comments => $fdata->{'comments'},
82968eb9 1063 ) or die $table->error;
1064
1065 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
1066
1067 for my $cdata ( @{ $fdata->{'constraints'} } ) {
1068 next unless $cdata->{'type'} eq 'foreign_key';
1069 $cdata->{'fields'} ||= [ $field->name ];
1070 push @{ $tdata->{'constraints'} }, $cdata;
1071 }
1072 }
1073
1074 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
1075 my $index = $table->add_index(
1076 name => $idata->{'name'},
1077 type => uc $idata->{'type'},
1078 fields => $idata->{'fields'},
3406fd5b 1079 ) or die $table->error . ' ' . $table->name;
82968eb9 1080 }
1081
1082 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
1083 my $constraint = $table->add_constraint(
1084 name => $cdata->{'name'},
1085 type => $cdata->{'type'},
1086 fields => $cdata->{'fields'},
1087 reference_table => $cdata->{'reference_table'},
1088 reference_fields => $cdata->{'reference_fields'},
1089 match_type => $cdata->{'match_type'} || '',
100684f3 1090 on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
1091 on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
b3384294 1092 expression => $cdata->{'expression'},
1093 ) or die "Can't add constraint of type '" .
1094 $cdata->{'type'} . "' to table '" . $table->name .
1095 "': " . $table->error;
82968eb9 1096 }
1097 }
1098
330e4686 1099 for my $vinfo (@{$result->{views}}) {
1100 my $sql = $vinfo->{sql};
1101 $sql =~ s/\A\s+|\s+\z//g;
1102 my $view = $schema->add_view (
1103 name => $vinfo->{view_name},
1104 sql => $sql,
1105 fields => $vinfo->{fields},
1106 );
1107
1108 $view->extra ( temporary => 1 ) if $vinfo->{is_temporary};
1109 }
1110
f62bd16c 1111 return 1;
4422e22a 1112}
1113
11141;
1115
82968eb9 1116# -------------------------------------------------------------------
1117# Rescue the drowning and tie your shoestrings.
1118# Henry David Thoreau
1119# -------------------------------------------------------------------
4422e22a 1120
1121=pod
1122
0efb6e1b 1123=head1 AUTHORS
4422e22a 1124
1125Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
b8ea6076 1126Allen Day E<lt>allenday@ucla.eduE<gt>.
4422e22a 1127
1128=head1 SEE ALSO
1129
1130perl(1), Parse::RecDescent.
1131
1132=cut