Adding patch from user.
[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
124b192c 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 ) {
f59b2c0e 451 $create .= " AS\n ${sql}\n";
296c2701 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
124b192c 531 sub create_index
532 {
533 my ($index, $options) = @_;
bfb5a568 534
124b192c 535 my $qt = $options->{quote_table_names} ||'';
536 my $qf = $options->{quote_field_names} ||'';
537 my $table_name = $index->table->name;
538# my $table_name_ur = $qt ? unreserve($table_name) : $table_name;
bfb5a568 539
124b192c 540 my ($index_def, @constraint_defs);
bfb5a568 541
124b192c 542 my $name = $index->name || '';
543 if ( $name ) {
544 $name = next_unused_name($name);
545 }
bfb5a568 546
124b192c 547 my $type = $index->type || NORMAL;
548 my @fields =
549 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 550 map { $qt ? $_ : unreserve($_, $table_name ) }
124b192c 551 $index->fields;
552 next unless @fields;
553
554 my $def_start = qq[CONSTRAINT "$name" ];
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 =
565 "CREATE INDEX ${qf}${name}${qf} on ${qt}${table_name}${qt} (".
566 join( ', ', map { qq[$qf$_$qf] } @fields ).
567 ')'
bfb5a568 568 ;
124b192c 569 }
570 else {
571 warn "Unknown index type ($type) on table $table_name.\n"
572 if $WARN;
573 }
bfb5a568 574
124b192c 575 return $index_def, \@constraint_defs;
576 }
bfb5a568 577
124b192c 578 sub create_constraint
579 {
580 my ($c, $options) = @_;
bfb5a568 581
124b192c 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);
bfb5a568 586
124b192c 587 my $name = $c->name || '';
588 if ( $name ) {
589 $name = next_unused_name($name);
590 }
bfb5a568 591
124b192c 592 my @fields =
593 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 594 map { $qt ? $_ : unreserve( $_, $table_name )}
124b192c 595 $c->fields;
bfb5a568 596
124b192c 597 my @rfields =
598 map { $_ =~ s/\(.+\)//; $_ }
f7abfd61 599 map { $qt ? $_ : unreserve( $_, $table_name )}
124b192c 600 $c->reference_fields;
bfb5a568 601
124b192c 602 next if !@fields && $c->type ne CHECK_C;
603 my $def_start = $name ? qq[CONSTRAINT "$name" ] : '';
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 ) {
609 $name = next_unused_name($name);
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;
bfb5a568 621
124b192c 622 if ( @rfields ) {
623 $def .= ' ('.$qf . join( $qf.', '.$qf, @rfields ) . $qf.')';
624 }
bfb5a568 625
124b192c 626 if ( $c->match_type ) {
627 $def .= ' MATCH ' .
628 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
629 }
bfb5a568 630
124b192c 631 if ( $c->on_delete ) {
632 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
633 }
5342f5c1 634
124b192c 635 if ( $c->on_update ) {
636 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
637 }
bfb5a568 638
124b192c 639 if ( $c->deferrable ) {
640 $def .= ' DEFERRABLE';
641 }
642
643 push @fks, "$def";
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
ad258776 683 if ( $data_type =~ /^time/i || $data_type =~ /^interval/i ) {
bfb5a568 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
ad258776 714 if (defined $size[0] && $size[0] > 0 && $data_type =~ /^time/i ) {
715 $data_type =~ s/^(time.*?)( with.*)?$/$1($size[0])/;
716 $data_type .= $2 if(defined $2);
717 } elsif ( defined $size[0] && $size[0] > 0 ) {
718 $data_type .= '(' . join( ',', @size ) . ')';
08d91aad 719 }
ad258776 720
bfb5a568 721
722
723 return $data_type;
724}
725
726
727sub alter_field
728{
729 my ($from_field, $to_field) = @_;
730
731 die "Can't alter field in another table"
732 if($from_field->table->name ne $to_field->table->name);
733
734 my @out;
3406fd5b 735 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
bfb5a568 736 $to_field->table->name,
737 $to_field->name) if(!$to_field->is_nullable and
738 $from_field->is_nullable);
739
90726ffd 740 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL',
741 $to_field->table->name,
742 $to_field->name)
743 if ( !$from_field->is_nullable and $to_field->is_nullable );
744
745
bfb5a568 746 my $from_dt = convert_datatype($from_field);
747 my $to_dt = convert_datatype($to_field);
3406fd5b 748 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
bfb5a568 749 $to_field->table->name,
750 $to_field->name,
751 $to_dt) if($to_dt ne $from_dt);
752
3406fd5b 753 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
bfb5a568 754 $to_field->table->name,
755 $from_field->name,
756 $to_field->name) if($from_field->name ne $to_field->name);
757
3406fd5b 758 my $old_default = $from_field->default_value;
759 my $new_default = $to_field->default_value;
90726ffd 760 my $default_value = $to_field->default_value;
761
762 # fixes bug where output like this was created:
763 # ALTER TABLE users ALTER COLUMN column SET DEFAULT ThisIsUnescaped;
764 if(ref $default_value eq "SCALAR" ) {
765 $default_value = $$default_value;
766 } elsif( defined $default_value && $to_dt =~ /^(character|text)/xsmi ) {
767 $default_value =~ s/'/''/xsmg;
768 $default_value = q(') . $default_value . q(');
769 }
770
3406fd5b 771 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
bfb5a568 772 $to_field->table->name,
773 $to_field->name,
90726ffd 774 $default_value)
3406fd5b 775 if ( defined $new_default &&
776 (!defined $old_default || $old_default ne $new_default) );
bfb5a568 777
90726ffd 778 # fixes bug where removing the DEFAULT statement of a column
779 # would result in no change
780
781 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
782 $to_field->table->name,
783 $to_field->name)
784 if ( !defined $new_default && defined $old_default );
785
786
bfb5a568 787 return wantarray ? @out : join("\n", @out);
bfb5a568 788}
789
3406fd5b 790sub rename_field { alter_field(@_) }
791
bfb5a568 792sub add_field
793{
794 my ($new_field) = @_;
795
3406fd5b 796 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
bfb5a568 797 $new_field->table->name,
798 create_field($new_field));
799 return $out;
800
801}
802
803sub drop_field
804{
805 my ($old_field) = @_;
806
3406fd5b 807 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
bfb5a568 808 $old_field->table->name,
809 $old_field->name);
810
811 return $out;
812}
813
3406fd5b 814sub alter_table {
815 my ($to_table, $options) = @_;
816 my $qt = $options->{quote_table_names} || '';
817 my $out = sprintf('ALTER TABLE %s %s',
818 $qt . $to_table->name . $qt,
819 $options->{alter_table_action});
820 return $out;
821}
822
823sub rename_table {
824 my ($old_table, $new_table, $options) = @_;
825 my $qt = $options->{quote_table_names} || '';
826 $options->{alter_table_action} = "RENAME TO $qt$new_table$qt";
827 return alter_table($old_table, $options);
828}
829
830sub alter_create_index {
831 my ($index, $options) = @_;
832 my $qt = $options->{quote_table_names} || '';
833 my $qf = $options->{quote_field_names} || '';
834 my ($idef, $constraints) = create_index($index, {
835 quote_field_names => $qf,
836 quote_table_names => $qt,
837 table_name => $index->table->name,
838 });
839 return $index->type eq NORMAL ? $idef
840 : sprintf('ALTER TABLE %s ADD %s',
841 $qt . $index->table->name . $qt,
842 join(q{}, @$constraints)
843 );
844}
845
846sub alter_drop_index {
847 my ($index, $options) = @_;
848 my $index_name = $index->name;
849 return "DROP INDEX $index_name";
850}
851
852sub alter_drop_constraint {
853 my ($c, $options) = @_;
854 my $qt = $options->{quote_table_names} || '';
855 my $qc = $options->{quote_field_names} || '';
856 my $out = sprintf('ALTER TABLE %s DROP CONSTRAINT %s',
857 $qt . $c->table->name . $qt,
858 $qc . $c->name . $qc );
859 return $out;
860}
861
862sub alter_create_constraint {
863 my ($index, $options) = @_;
864 my $qt = $options->{quote_table_names} || '';
90726ffd 865 my ($defs, $fks) = create_constraint(@_);
866
867 # return if there are no constraint definitions so we don't run
868 # into output like this:
869 # ALTER TABLE users ADD ;
870
871 return unless(@{$defs} || @{$fks});
872 return $index->type eq FOREIGN_KEY ? join(q{}, @{$fks})
3406fd5b 873 : join( ' ', 'ALTER TABLE', $qt.$index->table->name.$qt,
90726ffd 874 'ADD', join(q{}, @{$defs}, @{$fks})
3406fd5b 875 );
876}
877
878sub drop_table {
879 my ($table, $options) = @_;
880 my $qt = $options->{quote_table_names} || '';
881 return "DROP TABLE $qt$table$qt CASCADE";
882}
883
f8f0253c 8841;
f8f0253c 885
96844cae 886# -------------------------------------------------------------------
887# Life is full of misery, loneliness, and suffering --
888# and it's all over much too soon.
889# Woody Allen
890# -------------------------------------------------------------------
f8f0253c 891
96844cae 892=pod
f8f0253c 893
20770e44 894=head1 SEE ALSO
895
896SQL::Translator, SQL::Translator::Producer::Oracle.
897
f8f0253c 898=head1 AUTHOR
899
f997b9ab 900Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
f8f0253c 901
902=cut