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