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