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