Add parenthesis into the VIEW definition to make sure the pg parser still can deal...
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / PostgreSQL.pm
CommitLineData
f8f0253c 1package SQL::Translator::Producer::PostgreSQL;
2
3# -------------------------------------------------------------------
478f608d 4# Copyright (C) 2002-2009 SQLFairy Authors
f8f0253c 5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18# 02111-1307 USA
19# -------------------------------------------------------------------
20
96844cae 21=head1 NAME
22
23SQL::Translator::Producer::PostgreSQL - PostgreSQL producer for SQL::Translator
24
20770e44 25=head1 SYNOPSIS
26
27 my $t = SQL::Translator->new( parser => '...', producer => 'PostgreSQL' );
28 $t->translate;
29
30=head1 DESCRIPTION
31
32Creates a DDL suitable for PostgreSQL. Very heavily based on the Oracle
33producer.
34
96844cae 35=cut
36
f8f0253c 37use strict;
bfb5a568 38use warnings;
da06ac74 39use vars qw[ $DEBUG $WARN $VERSION %used_names ];
4ab3763d 40$VERSION = '1.59';
a25ac5d2 41$DEBUG = 0 unless defined $DEBUG;
f8f0253c 42
8d11f4cb 43use base qw(SQL::Translator::Producer);
0c43e0a1 44use SQL::Translator::Schema::Constants;
296c2701 45use SQL::Translator::Utils qw(debug header_comment);
f8f0253c 46use Data::Dumper;
47
bfb5a568 48my %translate;
49my $max_id_length;
50
51BEGIN {
52
53 %translate = (
d529894e 54 #
55 # MySQL types
56 #
57 bigint => 'bigint',
4328d7bd 58 double => 'numeric',
59 decimal => 'numeric',
60 float => 'numeric',
d529894e 61 int => 'integer',
62 mediumint => 'integer',
63 smallint => 'smallint',
64 tinyint => 'smallint',
c8c17a58 65 char => 'character',
da8e499e 66 varchar => 'character varying',
d529894e 67 longtext => 'text',
68 mediumtext => 'text',
69 text => 'text',
70 tinytext => 'text',
71 tinyblob => 'bytea',
72 blob => 'bytea',
73 mediumblob => 'bytea',
74 longblob => 'bytea',
da8e499e 75 enum => 'character varying',
76 set => 'character varying',
d529894e 77 date => 'date',
78 datetime => 'timestamp',
e56dabb7 79 time => 'time',
d529894e 80 timestamp => 'timestamp',
81 year => 'date',
82
83 #
84 # Oracle types
85 #
96844cae 86 number => 'integer',
c8c17a58 87 char => 'character',
da8e499e 88 varchar2 => 'character varying',
96844cae 89 long => 'text',
90 CLOB => 'bytea',
91 date => 'date',
92
93 #
94 # Sybase types
95 #
96 int => 'integer',
97 money => 'money',
da8e499e 98 varchar => 'character varying',
96844cae 99 datetime => 'timestamp',
100 text => 'text',
4328d7bd 101 real => 'numeric',
96844cae 102 comment => 'text',
103 bit => 'bit',
104 tinyint => 'smallint',
4328d7bd 105 float => 'numeric',
d529894e 106);
107
bfb5a568 108 $max_id_length = 62;
109}
96844cae 110my %reserved = map { $_, 1 } qw[
111 ALL ANALYSE ANALYZE AND ANY AS ASC
112 BETWEEN BINARY BOTH
113 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
114 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
115 DEFAULT DEFERRABLE DESC DISTINCT DO
116 ELSE END EXCEPT
117 FALSE FOR FOREIGN FREEZE FROM FULL
118 GROUP HAVING
119 ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL
120 JOIN LEADING LEFT LIKE LIMIT
121 NATURAL NEW NOT NOTNULL NULL
122 OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
123 PRIMARY PUBLIC REFERENCES RIGHT
124 SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
125 UNION UNIQUE USER USING VERBOSE WHEN WHERE
126];
d529894e 127
bfb5a568 128# my $max_id_length = 62;
96844cae 129my %used_identifiers = ();
130my %global_names;
131my %unreserve;
132my %truncated;
133
134=pod
135
136=head1 PostgreSQL Create Table Syntax
137
138 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
139 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
140 | table_constraint } [, ... ]
141 )
142 [ INHERITS ( parent_table [, ... ] ) ]
143 [ WITH OIDS | WITHOUT OIDS ]
144
145where column_constraint is:
146
147 [ CONSTRAINT constraint_name ]
148 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
149 CHECK (expression) |
150 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
151 [ ON DELETE action ] [ ON UPDATE action ] }
152 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
153
154and table_constraint is:
155
156 [ CONSTRAINT constraint_name ]
157 { UNIQUE ( column_name [, ... ] ) |
158 PRIMARY KEY ( column_name [, ... ] ) |
159 CHECK ( expression ) |
160 FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
161 [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
162 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
163
da8e499e 164=head1 Create Index Syntax
165
166 CREATE [ UNIQUE ] INDEX index_name ON table
167 [ USING acc_method ] ( column [ ops_name ] [, ...] )
168 [ WHERE predicate ]
169 CREATE [ UNIQUE ] INDEX index_name ON table
170 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
171 [ WHERE predicate ]
172
96844cae 173=cut
f8f0253c 174
96844cae 175# -------------------------------------------------------------------
f8f0253c 176sub produce {
a1d94525 177 my $translator = shift;
a25ac5d2 178 local $DEBUG = $translator->debug;
179 local $WARN = $translator->show_warnings;
a1d94525 180 my $no_comments = $translator->no_comments;
181 my $add_drop_table = $translator->add_drop_table;
182 my $schema = $translator->schema;
5342f5c1 183 my $pargs = $translator->producer_args;
999859d7 184 local %used_names = ();
5342f5c1 185
186 my $postgres_version = $pargs->{postgres_version} || 0;
96844cae 187
bfb5a568 188 my $qt = '';
189 $qt = '"' if ($translator->quote_table_names);
190 my $qf = '';
191 $qf = '"' if ($translator->quote_field_names);
192
bf75adec 193 my @output;
194 push @output, header_comment unless ($no_comments);
96844cae 195
08d91aad 196 my (@table_defs, @fks);
0c43e0a1 197 for my $table ( $schema->get_tables ) {
08d91aad 198
199 my ($table_def, $fks) = create_table($table,
200 { quote_table_names => $qt,
201 quote_field_names => $qf,
202 no_comments => $no_comments,
5342f5c1 203 postgres_version => $postgres_version,
08d91aad 204 add_drop_table => $add_drop_table,});
205 push @table_defs, $table_def;
206 push @fks, @$fks;
207
da8e499e 208 }
209
296c2701 210 for my $view ( $schema->get_views ) {
211 push @table_defs, create_view($view, {
a25ac5d2 212 add_drop_view => $add_drop_table,
296c2701 213 quote_table_names => $qt,
214 quote_field_names => $qf,
215 no_comments => $no_comments,
216 });
217 }
218
bf75adec 219 push @output, map { "$_;\n\n" } @table_defs;
08d91aad 220 if ( @fks ) {
bf75adec 221 push @output, "--\n-- Foreign Key Definitions\n--\n\n" unless $no_comments;
222 push @output, map { "$_;\n\n" } @fks;
08d91aad 223 }
021dbce8 224
da8e499e 225 if ( $WARN ) {
226 if ( %truncated ) {
227 warn "Truncated " . keys( %truncated ) . " names:\n";
228 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
229 }
230
231 if ( %unreserve ) {
232 warn "Encounted " . keys( %unreserve ) .
233 " unsafe names in schema (reserved or invalid):\n";
234 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
235 }
f8f0253c 236 }
237
bf75adec 238 return wantarray
239 ? @output
240 : join ('', @output);
f8f0253c 241}
242
96844cae 243# -------------------------------------------------------------------
244sub mk_name {
0c43e0a1 245 my $basename = shift || '';
246 my $type = shift || '';
247 my $scope = shift || '';
248 my $critical = shift || '';
96844cae 249 my $basename_orig = $basename;
bfb5a568 250# my $max_id_length = 62;
2ad4c2c8 251 my $max_name = $type
252 ? $max_id_length - (length($type) + 1)
253 : $max_id_length;
96844cae 254 $basename = substr( $basename, 0, $max_name )
255 if length( $basename ) > $max_name;
256 my $name = $type ? "${type}_$basename" : $basename;
257
258 if ( $basename ne $basename_orig and $critical ) {
259 my $show_type = $type ? "+'$type'" : "";
260 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
261 "character limit to make '$name'\n" if $WARN;
262 $truncated{ $basename_orig } = $name;
263 }
264
265 $scope ||= \%global_names;
266 if ( my $prev = $scope->{ $name } ) {
267 my $name_orig = $name;
268 $name .= sprintf( "%02d", ++$prev );
269 substr($name, $max_id_length - 3) = "00"
270 if length( $name ) > $max_id_length;
271
272 warn "The name '$name_orig' has been changed to ",
273 "'$name' to make it unique.\n" if $WARN;
274
275 $scope->{ $name_orig }++;
f8f0253c 276 }
96844cae 277
278 $scope->{ $name }++;
279 return $name;
280}
281
282# -------------------------------------------------------------------
283sub unreserve {
0c43e0a1 284 my $name = shift || '';
285 my $schema_obj_name = shift || '';
286
96844cae 287 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
288
289 # also trap fields that don't begin with a letter
bfb5a568 290 return $name if (!$reserved{ uc $name }) && $name =~ /^[a-z]/i;
96844cae 291
292 if ( $schema_obj_name ) {
293 ++$unreserve{"$schema_obj_name.$name"};
294 }
295 else {
296 ++$unreserve{"$name (table name)"};
297 }
298
299 my $unreserve = sprintf '%s_', $name;
300 return $unreserve.$suffix;
f8f0253c 301}
302
50840472 303# -------------------------------------------------------------------
304sub next_unused_name {
999859d7 305 my $name = shift || '';
306 if ( !defined( $used_names{$name} ) ) {
50840472 307 $used_names{$name} = $name;
308 return $name;
309 }
999859d7 310
50840472 311 my $i = 2;
999859d7 312 while ( defined( $used_names{ $name . $i } ) ) {
50840472 313 ++$i;
314 }
315 $name .= $i;
316 $used_names{$name} = $name;
317 return $name;
318}
319
999859d7 320
bfb5a568 321sub create_table
322{
323 my ($table, $options) = @_;
324
325 my $qt = $options->{quote_table_names} || '';
326 my $qf = $options->{quote_field_names} || '';
327 my $no_comments = $options->{no_comments} || 0;
328 my $add_drop_table = $options->{add_drop_table} || 0;
5342f5c1 329 my $postgres_version = $options->{postgres_version} || 0;
bfb5a568 330
3406fd5b 331 my $table_name = $table->name or next;
3406fd5b 332 my ( $fql_tbl_name ) = ( $table_name =~ s/\W(.*)$// ) ? $1 : q{};
333 my $table_name_ur = $qt ? $table_name
334 : $fql_tbl_name ? join('.', $table_name, unreserve($fql_tbl_name))
335 : unreserve($table_name);
bfb5a568 336 $table->name($table_name_ur);
337
338# print STDERR "$table_name table_name\n";
5342f5c1 339 my ( @comments, @field_defs, @sequence_defs, @constraint_defs, @type_defs, @type_drops, @fks );
bfb5a568 340
7ed7402c 341 push @comments, "--\n-- Table: $table_name_ur\n--\n" unless $no_comments;
bfb5a568 342
343 if ( $table->comments and !$no_comments ){
344 my $c = "-- Comments: \n-- ";
345 $c .= join "\n-- ", $table->comments;
7ed7402c 346 $c .= "\n--\n";
bfb5a568 347 push @comments, $c;
348 }
349
350 #
351 # Fields
352 #
353 my %field_name_scope;
354 for my $field ( $table->get_fields ) {
355 push @field_defs, create_field($field, { quote_table_names => $qt,
356 quote_field_names => $qf,
357 table_name => $table_name_ur,
5342f5c1 358 postgres_version => $postgres_version,
359 type_defs => \@type_defs,
360 type_drops => \@type_drops,
bfb5a568 361 constraint_defs => \@constraint_defs,});
362 }
363
364 #
365 # Index Declarations
366 #
367 my @index_defs = ();
368 # my $idx_name_default;
369 for my $index ( $table->get_indices ) {
370 my ($idef, $constraints) = create_index($index,
371 {
372 quote_field_names => $qf,
373 quote_table_names => $qt,
374 table_name => $table_name,
375 });
7ed7402c 376 $idef and push @index_defs, $idef;
bfb5a568 377 push @constraint_defs, @$constraints;
378 }
379
380 #
381 # Table constraints
382 #
383 my $c_name_default;
384 for my $c ( $table->get_constraints ) {
385 my ($cdefs, $fks) = create_constraint($c,
386 {
387 quote_field_names => $qf,
388 quote_table_names => $qt,
389 table_name => $table_name,
390 });
391 push @constraint_defs, @$cdefs;
392 push @fks, @$fks;
393 }
394
3e98f7d9 395
396 my $temporary = "";
397
398 if(exists $table->{extra}{temporary}) {
399 $temporary = $table->{extra}{temporary} ? "TEMPORARY " : "";
400 }
401
bfb5a568 402 my $create_statement;
403 $create_statement = join("\n", @comments);
5342f5c1 404 if ($add_drop_table) {
405 if ($postgres_version >= 8.2) {
406 $create_statement .= qq[DROP TABLE IF EXISTS $qt$table_name_ur$qt CASCADE;\n];
938464ee 407 $create_statement .= join (";\n", @type_drops) . ";\n"
408 if $postgres_version >= 8.3 && scalar @type_drops;
5342f5c1 409 } else {
410 $create_statement .= qq[DROP TABLE $qt$table_name_ur$qt CASCADE;\n];
411 }
412 }
938464ee 413 $create_statement .= join(";\n", @type_defs) . ";\n"
414 if $postgres_version >= 8.3 && scalar @type_defs;
3e98f7d9 415 $create_statement .= qq[CREATE ${temporary}TABLE $qt$table_name_ur$qt (\n].
bfb5a568 416 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
3406fd5b 417 "\n)"
bfb5a568 418 ;
3406fd5b 419 $create_statement .= @index_defs ? ';' : q{};
420 $create_statement .= ( $create_statement =~ /;$/ ? "\n" : q{} )
421 . join(";\n", @index_defs);
bfb5a568 422
08d91aad 423 return $create_statement, \@fks;
bfb5a568 424}
425
296c2701 426sub create_view {
427 my ($view, $options) = @_;
428 my $qt = $options->{quote_table_names} || '';
429 my $qf = $options->{quote_field_names} || '';
a25ac5d2 430 my $add_drop_view = $options->{add_drop_view};
296c2701 431
432 my $view_name = $view->name;
433 debug("PKG: Looking at view '${view_name}'\n");
434
435 my $create = '';
436 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n"
437 unless $options->{no_comments};
a25ac5d2 438 $create .= "DROP VIEW ${qt}${view_name}${qt};\n" if $add_drop_view;
296c2701 439 $create .= 'CREATE';
296c2701 440
441 my $extra = $view->extra;
442 $create .= " TEMPORARY" if exists($extra->{temporary}) && $extra->{temporary};
443 $create .= " VIEW ${qt}${view_name}${qt}";
444
445 if ( my @fields = $view->fields ) {
446 my $field_list = join ', ', map { "${qf}${_}${qf}" } @fields;
447 $create .= " ( ${field_list} )";
448 }
449
450 if ( my $sql = $view->sql ) {
451 $create .= " AS (\n ${sql}\n )";
452 }
453
454 if ( $extra->{check_option} ) {
455 $create .= ' WITH ' . uc $extra->{check_option} . ' CHECK OPTION';
456 }
457
296c2701 458 return $create;
459}
460
bfb5a568 461{
462
463 my %field_name_scope;
464
465 sub create_field
466 {
467 my ($field, $options) = @_;
468
469 my $qt = $options->{quote_table_names} || '';
470 my $qf = $options->{quote_field_names} || '';
471 my $table_name = $field->table->name;
472 my $constraint_defs = $options->{constraint_defs} || [];
5342f5c1 473 my $postgres_version = $options->{postgres_version} || 0;
474 my $type_defs = $options->{type_defs} || [];
475 my $type_drops = $options->{type_drops} || [];
bfb5a568 476
477 $field_name_scope{$table_name} ||= {};
912e67a1 478 my $field_name = $field->name;
08d91aad 479 my $field_name_ur = $qf ? $field_name : unreserve($field_name, $table_name );
bfb5a568 480 $field->name($field_name_ur);
481 my $field_comments = $field->comments
482 ? "-- " . $field->comments . "\n "
483 : '';
484
485 my $field_def = $field_comments.qq[$qf$field_name_ur$qf];
486
487 #
488 # Datatype
489 #
490 my @size = $field->size;
491 my $data_type = lc $field->data_type;
492 my %extra = $field->extra;
493 my $list = $extra{'list'} || [];
494 # todo deal with embedded quotes
495 my $commalist = join( ', ', map { qq['$_'] } @$list );
bfb5a568 496
5342f5c1 497 if ($postgres_version >= 8.3 && $field->data_type eq 'enum') {
498 my $type_name = $field->table->name . '_' . $field->name . '_type';
499 $field_def .= ' '. $type_name;
3406fd5b 500 push @$type_defs, "CREATE TYPE $type_name AS ENUM ($commalist)";
501 push @$type_drops, "DROP TYPE IF EXISTS $type_name";
5342f5c1 502 } else {
503 $field_def .= ' '. convert_datatype($field);
504 }
bfb5a568 505
506 #
bc8e2aa1 507 # Default value
bfb5a568 508 #
f39e9c12 509 my $default = $field->default_value;
bfb5a568 510 if ( defined $default ) {
bc8e2aa1 511 SQL::Translator::Producer->_apply_default_value(
512 \$field_def,
513 $default,
514 [
515 'NULL' => \'NULL',
516 'now()' => 'now()',
517 'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
518 ],
519 );
bfb5a568 520 }
521
522 #
523 # Not null constraint
524 #
525 $field_def .= ' NOT NULL' unless $field->is_nullable;
526
527 return $field_def;
528 }
529}
530
bfb5a568 531 sub create_index
532 {
533 my ($index, $options) = @_;
534
535 my $qt = $options->{quote_table_names} ||'';
536 my $qf = $options->{quote_field_names} ||'';
537 my $table_name = $index->table->name;
08d91aad 538# my $table_name_ur = $qt ? unreserve($table_name) : $table_name;
bfb5a568 539
540 my ($index_def, @constraint_defs);
541
bfb5a568 542 my $name = $index->name || '';
543 if ( $name ) {
999859d7 544 $name = next_unused_name($name);
bfb5a568 545 }
546
547 my $type = $index->type || NORMAL;
548 my @fields =
549 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 550 map { $qt ? $_ : unreserve($_, $table_name ) }
bfb5a568 551 $index->fields;
552 next unless @fields;
553
3406fd5b 554 my $def_start = qq[CONSTRAINT "$name" ];
bfb5a568 555 if ( $type eq PRIMARY_KEY ) {
556 push @constraint_defs, "${def_start}PRIMARY KEY ".
557 '(' .$qf . join( $qf. ', '.$qf, @fields ) . $qf . ')';
558 }
559 elsif ( $type eq UNIQUE ) {
560 push @constraint_defs, "${def_start}UNIQUE " .
561 '(' . $qf . join( $qf. ', '.$qf, @fields ) . $qf.')';
562 }
563 elsif ( $type eq NORMAL ) {
564 $index_def =
08d91aad 565 "CREATE INDEX ${qf}${name}${qf} on ${qt}${table_name}${qt} (".
bfb5a568 566 join( ', ', map { qq[$qf$_$qf] } @fields ).
3406fd5b 567 ')'
bfb5a568 568 ;
569 }
570 else {
571 warn "Unknown index type ($type) on table $table_name.\n"
572 if $WARN;
573 }
574
575 return $index_def, \@constraint_defs;
576 }
577
578 sub create_constraint
579 {
580 my ($c, $options) = @_;
581
582 my $qf = $options->{quote_field_names} ||'';
583 my $qt = $options->{quote_table_names} ||'';
584 my $table_name = $c->table->name;
585 my (@constraint_defs, @fks);
586
587 my $name = $c->name || '';
588 if ( $name ) {
999859d7 589 $name = next_unused_name($name);
bfb5a568 590 }
591
592 my @fields =
593 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 594 map { $qt ? $_ : unreserve( $_, $table_name )}
bfb5a568 595 $c->fields;
596
597 my @rfields =
598 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 599 map { $qt ? $_ : unreserve( $_, $table_name )}
bfb5a568 600 $c->reference_fields;
601
602 next if !@fields && $c->type ne CHECK_C;
3406fd5b 603 my $def_start = $name ? qq[CONSTRAINT "$name" ] : '';
bfb5a568 604 if ( $c->type eq PRIMARY_KEY ) {
605 push @constraint_defs, "${def_start}PRIMARY KEY ".
606 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
607 }
608 elsif ( $c->type eq UNIQUE ) {
999859d7 609 $name = next_unused_name($name);
bfb5a568 610 push @constraint_defs, "${def_start}UNIQUE " .
611 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
612 }
613 elsif ( $c->type eq CHECK_C ) {
614 my $expression = $c->expression;
615 push @constraint_defs, "${def_start}CHECK ($expression)";
616 }
617 elsif ( $c->type eq FOREIGN_KEY ) {
618 my $def .= "ALTER TABLE ${qt}${table_name}${qt} ADD FOREIGN KEY (" .
619 join( ', ', map { qq[$qf$_$qf] } @fields ) . ')' .
620 "\n REFERENCES " . $qt . $c->reference_table . $qt;
621
622 if ( @rfields ) {
623 $def .= ' ('.$qf . join( $qf.', '.$qf, @rfields ) . $qf.')';
624 }
625
626 if ( $c->match_type ) {
627 $def .= ' MATCH ' .
628 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
629 }
630
631 if ( $c->on_delete ) {
632 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
633 }
634
635 if ( $c->on_update ) {
636 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
637 }
638
5342f5c1 639 if ( $c->deferrable ) {
640 $def .= ' DEFERRABLE';
641 }
642
3406fd5b 643 push @fks, "$def";
bfb5a568 644 }
645
646 return \@constraint_defs, \@fks;
647 }
bfb5a568 648
649sub convert_datatype
650{
651 my ($field) = @_;
652
653 my @size = $field->size;
654 my $data_type = lc $field->data_type;
655
656 if ( $data_type eq 'enum' ) {
657# my $len = 0;
658# $len = ($len < length($_)) ? length($_) : $len for (@$list);
659# my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
660# push @$constraint_defs,
3406fd5b 661# qq[CONSTRAINT "$chk_name" CHECK ($qf$field_name$qf ].
bfb5a568 662# qq[IN ($commalist))];
663 $data_type = 'character varying';
664 }
665 elsif ( $data_type eq 'set' ) {
666 $data_type = 'character varying';
667 }
668 elsif ( $field->is_auto_increment ) {
669 if ( defined $size[0] && $size[0] > 11 ) {
670 $data_type = 'bigserial';
671 }
672 else {
673 $data_type = 'serial';
674 }
675 undef @size;
676 }
677 else {
678 $data_type = defined $translate{ $data_type } ?
679 $translate{ $data_type } :
680 $data_type;
681 }
682
683 if ( $data_type =~ /timestamp/i ) {
684 if ( defined $size[0] && $size[0] > 6 ) {
685 $size[0] = 6;
686 }
687 }
688
689 if ( $data_type eq 'integer' ) {
690 if ( defined $size[0] && $size[0] > 0) {
691 if ( $size[0] > 10 ) {
692 $data_type = 'bigint';
693 }
694 elsif ( $size[0] < 5 ) {
695 $data_type = 'smallint';
696 }
697 else {
698 $data_type = 'integer';
699 }
700 }
701 else {
702 $data_type = 'integer';
703 }
704 }
e56dabb7 705 my @type_without_size = qw/bigint boolean box bytea cidr circle date inet
706 integer smallint text line lseg macaddr money
707 path point polygon real/;
708 foreach (@type_without_size) {
709 if ( $data_type =~ qr/$_/ ) {
710 undef @size; last;
711 }
712 }
bfb5a568 713
bfb5a568 714 if ( defined $size[0] && $size[0] > 0 ) {
715 $data_type .= '(' . join( ',', @size ) . ')';
716 }
08d91aad 717 elsif (defined $size[0] && $data_type eq 'timestamp' ) {
718 $data_type .= '(' . join( ',', @size ) . ')';
719 }
bfb5a568 720
721
722 return $data_type;
723}
724
725
726sub alter_field
727{
728 my ($from_field, $to_field) = @_;
729
730 die "Can't alter field in another table"
731 if($from_field->table->name ne $to_field->table->name);
732
733 my @out;
3406fd5b 734 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
bfb5a568 735 $to_field->table->name,
736 $to_field->name) if(!$to_field->is_nullable and
737 $from_field->is_nullable);
738
90726ffd 739 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL',
740 $to_field->table->name,
741 $to_field->name)
742 if ( !$from_field->is_nullable and $to_field->is_nullable );
743
744
bfb5a568 745 my $from_dt = convert_datatype($from_field);
746 my $to_dt = convert_datatype($to_field);
3406fd5b 747 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
bfb5a568 748 $to_field->table->name,
749 $to_field->name,
750 $to_dt) if($to_dt ne $from_dt);
751
3406fd5b 752 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
bfb5a568 753 $to_field->table->name,
754 $from_field->name,
755 $to_field->name) if($from_field->name ne $to_field->name);
756
3406fd5b 757 my $old_default = $from_field->default_value;
758 my $new_default = $to_field->default_value;
90726ffd 759 my $default_value = $to_field->default_value;
760
761 # fixes bug where output like this was created:
762 # ALTER TABLE users ALTER COLUMN column SET DEFAULT ThisIsUnescaped;
763 if(ref $default_value eq "SCALAR" ) {
764 $default_value = $$default_value;
765 } elsif( defined $default_value && $to_dt =~ /^(character|text)/xsmi ) {
766 $default_value =~ s/'/''/xsmg;
767 $default_value = q(') . $default_value . q(');
768 }
769
3406fd5b 770 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
bfb5a568 771 $to_field->table->name,
772 $to_field->name,
90726ffd 773 $default_value)
3406fd5b 774 if ( defined $new_default &&
775 (!defined $old_default || $old_default ne $new_default) );
bfb5a568 776
90726ffd 777 # fixes bug where removing the DEFAULT statement of a column
778 # would result in no change
779
780 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
781 $to_field->table->name,
782 $to_field->name)
783 if ( !defined $new_default && defined $old_default );
784
785
bfb5a568 786 return wantarray ? @out : join("\n", @out);
bfb5a568 787}
788
3406fd5b 789sub rename_field { alter_field(@_) }
790
bfb5a568 791sub add_field
792{
793 my ($new_field) = @_;
794
3406fd5b 795 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
bfb5a568 796 $new_field->table->name,
797 create_field($new_field));
798 return $out;
799
800}
801
802sub drop_field
803{
804 my ($old_field) = @_;
805
3406fd5b 806 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
bfb5a568 807 $old_field->table->name,
808 $old_field->name);
809
810 return $out;
811}
812
3406fd5b 813sub alter_table {
814 my ($to_table, $options) = @_;
815 my $qt = $options->{quote_table_names} || '';
816 my $out = sprintf('ALTER TABLE %s %s',
817 $qt . $to_table->name . $qt,
818 $options->{alter_table_action});
819 return $out;
820}
821
822sub rename_table {
823 my ($old_table, $new_table, $options) = @_;
824 my $qt = $options->{quote_table_names} || '';
825 $options->{alter_table_action} = "RENAME TO $qt$new_table$qt";
826 return alter_table($old_table, $options);
827}
828
829sub alter_create_index {
830 my ($index, $options) = @_;
831 my $qt = $options->{quote_table_names} || '';
832 my $qf = $options->{quote_field_names} || '';
833 my ($idef, $constraints) = create_index($index, {
834 quote_field_names => $qf,
835 quote_table_names => $qt,
836 table_name => $index->table->name,
837 });
838 return $index->type eq NORMAL ? $idef
839 : sprintf('ALTER TABLE %s ADD %s',
840 $qt . $index->table->name . $qt,
841 join(q{}, @$constraints)
842 );
843}
844
845sub alter_drop_index {
846 my ($index, $options) = @_;
847 my $index_name = $index->name;
848 return "DROP INDEX $index_name";
849}
850
851sub alter_drop_constraint {
852 my ($c, $options) = @_;
853 my $qt = $options->{quote_table_names} || '';
854 my $qc = $options->{quote_field_names} || '';
855 my $out = sprintf('ALTER TABLE %s DROP CONSTRAINT %s',
856 $qt . $c->table->name . $qt,
857 $qc . $c->name . $qc );
858 return $out;
859}
860
861sub alter_create_constraint {
862 my ($index, $options) = @_;
863 my $qt = $options->{quote_table_names} || '';
90726ffd 864 my ($defs, $fks) = create_constraint(@_);
865
866 # return if there are no constraint definitions so we don't run
867 # into output like this:
868 # ALTER TABLE users ADD ;
869
870 return unless(@{$defs} || @{$fks});
871 return $index->type eq FOREIGN_KEY ? join(q{}, @{$fks})
3406fd5b 872 : join( ' ', 'ALTER TABLE', $qt.$index->table->name.$qt,
90726ffd 873 'ADD', join(q{}, @{$defs}, @{$fks})
3406fd5b 874 );
875}
876
877sub drop_table {
878 my ($table, $options) = @_;
879 my $qt = $options->{quote_table_names} || '';
880 return "DROP TABLE $qt$table$qt CASCADE";
881}
882
f8f0253c 8831;
f8f0253c 884
96844cae 885# -------------------------------------------------------------------
886# Life is full of misery, loneliness, and suffering --
887# and it's all over much too soon.
888# Woody Allen
889# -------------------------------------------------------------------
f8f0253c 890
96844cae 891=pod
f8f0253c 892
20770e44 893=head1 SEE ALSO
894
895SQL::Translator, SQL::Translator::Producer::Oracle.
896
f8f0253c 897=head1 AUTHOR
898
20770e44 899Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
f8f0253c 900
901=cut