- Added some stuff to MANIFEST.SKIP
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / PostgreSQL.pm
CommitLineData
f8f0253c 1package SQL::Translator::Producer::PostgreSQL;
2
3# -------------------------------------------------------------------
821a0fde 4# $Id$
f8f0253c 5# -------------------------------------------------------------------
977651a5 6# Copyright (C) 2002-4 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;
999859d7 41use vars qw[ $DEBUG $WARN $VERSION %used_names ];
821a0fde 42$VERSION = sprintf "%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/;
a25ac5d2 43$DEBUG = 0 unless defined $DEBUG;
f8f0253c 44
8d11f4cb 45use base qw(SQL::Translator::Producer);
0c43e0a1 46use SQL::Translator::Schema::Constants;
296c2701 47use SQL::Translator::Utils qw(debug header_comment);
f8f0253c 48use Data::Dumper;
49
bfb5a568 50my %translate;
51my $max_id_length;
52
53BEGIN {
54
55 %translate = (
d529894e 56 #
57 # MySQL types
58 #
59 bigint => 'bigint',
4328d7bd 60 double => 'numeric',
61 decimal => 'numeric',
62 float => 'numeric',
d529894e 63 int => 'integer',
64 mediumint => 'integer',
65 smallint => 'smallint',
66 tinyint => 'smallint',
c8c17a58 67 char => 'character',
da8e499e 68 varchar => 'character varying',
d529894e 69 longtext => 'text',
70 mediumtext => 'text',
71 text => 'text',
72 tinytext => 'text',
73 tinyblob => 'bytea',
74 blob => 'bytea',
75 mediumblob => 'bytea',
76 longblob => 'bytea',
da8e499e 77 enum => 'character varying',
78 set => 'character varying',
d529894e 79 date => 'date',
80 datetime => 'timestamp',
e56dabb7 81 time => 'time',
d529894e 82 timestamp => 'timestamp',
83 year => 'date',
84
85 #
86 # Oracle types
87 #
96844cae 88 number => 'integer',
c8c17a58 89 char => 'character',
da8e499e 90 varchar2 => 'character varying',
96844cae 91 long => 'text',
92 CLOB => 'bytea',
93 date => 'date',
94
95 #
96 # Sybase types
97 #
98 int => 'integer',
99 money => 'money',
da8e499e 100 varchar => 'character varying',
96844cae 101 datetime => 'timestamp',
102 text => 'text',
4328d7bd 103 real => 'numeric',
96844cae 104 comment => 'text',
105 bit => 'bit',
106 tinyint => 'smallint',
4328d7bd 107 float => 'numeric',
d529894e 108);
109
bfb5a568 110 $max_id_length = 62;
111}
96844cae 112my %reserved = map { $_, 1 } qw[
113 ALL ANALYSE ANALYZE AND ANY AS ASC
114 BETWEEN BINARY BOTH
115 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
116 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
117 DEFAULT DEFERRABLE DESC DISTINCT DO
118 ELSE END EXCEPT
119 FALSE FOR FOREIGN FREEZE FROM FULL
120 GROUP HAVING
121 ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL
122 JOIN LEADING LEFT LIKE LIMIT
123 NATURAL NEW NOT NOTNULL NULL
124 OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
125 PRIMARY PUBLIC REFERENCES RIGHT
126 SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
127 UNION UNIQUE USER USING VERBOSE WHEN WHERE
128];
d529894e 129
bfb5a568 130# my $max_id_length = 62;
96844cae 131my %used_identifiers = ();
132my %global_names;
133my %unreserve;
134my %truncated;
135
136=pod
137
138=head1 PostgreSQL Create Table Syntax
139
140 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
141 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
142 | table_constraint } [, ... ]
143 )
144 [ INHERITS ( parent_table [, ... ] ) ]
145 [ WITH OIDS | WITHOUT OIDS ]
146
147where column_constraint is:
148
149 [ CONSTRAINT constraint_name ]
150 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
151 CHECK (expression) |
152 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
153 [ ON DELETE action ] [ ON UPDATE action ] }
154 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
155
156and table_constraint is:
157
158 [ CONSTRAINT constraint_name ]
159 { UNIQUE ( column_name [, ... ] ) |
160 PRIMARY KEY ( column_name [, ... ] ) |
161 CHECK ( expression ) |
162 FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
163 [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
164 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
165
da8e499e 166=head1 Create Index Syntax
167
168 CREATE [ UNIQUE ] INDEX index_name ON table
169 [ USING acc_method ] ( column [ ops_name ] [, ...] )
170 [ WHERE predicate ]
171 CREATE [ UNIQUE ] INDEX index_name ON table
172 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
173 [ WHERE predicate ]
174
96844cae 175=cut
f8f0253c 176
96844cae 177# -------------------------------------------------------------------
f8f0253c 178sub produce {
a1d94525 179 my $translator = shift;
a25ac5d2 180 local $DEBUG = $translator->debug;
181 local $WARN = $translator->show_warnings;
a1d94525 182 my $no_comments = $translator->no_comments;
183 my $add_drop_table = $translator->add_drop_table;
184 my $schema = $translator->schema;
5342f5c1 185 my $pargs = $translator->producer_args;
999859d7 186 local %used_names = ();
5342f5c1 187
188 my $postgres_version = $pargs->{postgres_version} || 0;
96844cae 189
bfb5a568 190 my $qt = '';
191 $qt = '"' if ($translator->quote_table_names);
192 my $qf = '';
193 $qf = '"' if ($translator->quote_field_names);
194
da8e499e 195 my $output;
5ee19df8 196 $output .= header_comment unless ($no_comments);
96844cae 197
08d91aad 198 my (@table_defs, @fks);
0c43e0a1 199 for my $table ( $schema->get_tables ) {
08d91aad 200
201 my ($table_def, $fks) = create_table($table,
202 { quote_table_names => $qt,
203 quote_field_names => $qf,
204 no_comments => $no_comments,
5342f5c1 205 postgres_version => $postgres_version,
08d91aad 206 add_drop_table => $add_drop_table,});
207 push @table_defs, $table_def;
208 push @fks, @$fks;
209
da8e499e 210 }
211
296c2701 212 for my $view ( $schema->get_views ) {
213 push @table_defs, create_view($view, {
a25ac5d2 214 add_drop_view => $add_drop_table,
296c2701 215 quote_table_names => $qt,
216 quote_field_names => $qf,
217 no_comments => $no_comments,
218 });
219 }
220
3406fd5b 221 $output = join(";\n\n", @table_defs) . ";\n\n";
08d91aad 222 if ( @fks ) {
223 $output .= "--\n-- Foreign Key Definitions\n--\n\n" unless $no_comments;
3406fd5b 224 $output .= join( ";\n\n", @fks ) . ";\n";
08d91aad 225 }
021dbce8 226
da8e499e 227 if ( $WARN ) {
228 if ( %truncated ) {
229 warn "Truncated " . keys( %truncated ) . " names:\n";
230 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
231 }
232
233 if ( %unreserve ) {
234 warn "Encounted " . keys( %unreserve ) .
235 " unsafe names in schema (reserved or invalid):\n";
236 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
237 }
f8f0253c 238 }
239
da8e499e 240 return $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;
332 $table_name = mk_name( $table_name, '', undef, 1 );
333 my ( $fql_tbl_name ) = ( $table_name =~ s/\W(.*)$// ) ? $1 : q{};
334 my $table_name_ur = $qt ? $table_name
335 : $fql_tbl_name ? join('.', $table_name, unreserve($fql_tbl_name))
336 : unreserve($table_name);
bfb5a568 337 $table->name($table_name_ur);
338
339# print STDERR "$table_name table_name\n";
5342f5c1 340 my ( @comments, @field_defs, @sequence_defs, @constraint_defs, @type_defs, @type_drops, @fks );
bfb5a568 341
7ed7402c 342 push @comments, "--\n-- Table: $table_name_ur\n--\n" unless $no_comments;
bfb5a568 343
344 if ( $table->comments and !$no_comments ){
345 my $c = "-- Comments: \n-- ";
346 $c .= join "\n-- ", $table->comments;
7ed7402c 347 $c .= "\n--\n";
bfb5a568 348 push @comments, $c;
349 }
350
351 #
352 # Fields
353 #
354 my %field_name_scope;
355 for my $field ( $table->get_fields ) {
356 push @field_defs, create_field($field, { quote_table_names => $qt,
357 quote_field_names => $qf,
358 table_name => $table_name_ur,
5342f5c1 359 postgres_version => $postgres_version,
360 type_defs => \@type_defs,
361 type_drops => \@type_drops,
bfb5a568 362 constraint_defs => \@constraint_defs,});
363 }
364
365 #
366 # Index Declarations
367 #
368 my @index_defs = ();
369 # my $idx_name_default;
370 for my $index ( $table->get_indices ) {
371 my ($idef, $constraints) = create_index($index,
372 {
373 quote_field_names => $qf,
374 quote_table_names => $qt,
375 table_name => $table_name,
376 });
7ed7402c 377 $idef and push @index_defs, $idef;
bfb5a568 378 push @constraint_defs, @$constraints;
379 }
380
381 #
382 # Table constraints
383 #
384 my $c_name_default;
385 for my $c ( $table->get_constraints ) {
386 my ($cdefs, $fks) = create_constraint($c,
387 {
388 quote_field_names => $qf,
389 quote_table_names => $qt,
390 table_name => $table_name,
391 });
392 push @constraint_defs, @$cdefs;
393 push @fks, @$fks;
394 }
395
3e98f7d9 396
397 my $temporary = "";
398
399 if(exists $table->{extra}{temporary}) {
400 $temporary = $table->{extra}{temporary} ? "TEMPORARY " : "";
401 }
402
bfb5a568 403 my $create_statement;
404 $create_statement = join("\n", @comments);
5342f5c1 405 if ($add_drop_table) {
406 if ($postgres_version >= 8.2) {
407 $create_statement .= qq[DROP TABLE IF EXISTS $qt$table_name_ur$qt CASCADE;\n];
408 $create_statement .= join ("\n", @type_drops) . "\n"
409 if $postgres_version >= 8.3;
410 } else {
411 $create_statement .= qq[DROP TABLE $qt$table_name_ur$qt CASCADE;\n];
412 }
413 }
414 $create_statement .= join("\n", @type_defs) . "\n"
415 if $postgres_version >= 8.3;
3e98f7d9 416 $create_statement .= qq[CREATE ${temporary}TABLE $qt$table_name_ur$qt (\n].
bfb5a568 417 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
3406fd5b 418 "\n)"
bfb5a568 419 ;
3406fd5b 420 $create_statement .= @index_defs ? ';' : q{};
421 $create_statement .= ( $create_statement =~ /;$/ ? "\n" : q{} )
422 . join(";\n", @index_defs);
bfb5a568 423
08d91aad 424 return $create_statement, \@fks;
bfb5a568 425}
426
296c2701 427sub create_view {
428 my ($view, $options) = @_;
429 my $qt = $options->{quote_table_names} || '';
430 my $qf = $options->{quote_field_names} || '';
a25ac5d2 431 my $add_drop_view = $options->{add_drop_view};
296c2701 432
433 my $view_name = $view->name;
434 debug("PKG: Looking at view '${view_name}'\n");
435
436 my $create = '';
437 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n"
438 unless $options->{no_comments};
a25ac5d2 439 $create .= "DROP VIEW ${qt}${view_name}${qt};\n" if $add_drop_view;
296c2701 440 $create .= 'CREATE';
296c2701 441
442 my $extra = $view->extra;
443 $create .= " TEMPORARY" if exists($extra->{temporary}) && $extra->{temporary};
444 $create .= " VIEW ${qt}${view_name}${qt}";
445
446 if ( my @fields = $view->fields ) {
447 my $field_list = join ', ', map { "${qf}${_}${qf}" } @fields;
448 $create .= " ( ${field_list} )";
449 }
450
451 if ( my $sql = $view->sql ) {
452 $create .= " AS (\n ${sql}\n )";
453 }
454
455 if ( $extra->{check_option} ) {
456 $create .= ' WITH ' . uc $extra->{check_option} . ' CHECK OPTION';
457 }
458
296c2701 459 return $create;
460}
461
bfb5a568 462{
463
464 my %field_name_scope;
465
466 sub create_field
467 {
468 my ($field, $options) = @_;
469
470 my $qt = $options->{quote_table_names} || '';
471 my $qf = $options->{quote_field_names} || '';
472 my $table_name = $field->table->name;
473 my $constraint_defs = $options->{constraint_defs} || [];
5342f5c1 474 my $postgres_version = $options->{postgres_version} || 0;
475 my $type_defs = $options->{type_defs} || [];
476 my $type_drops = $options->{type_drops} || [];
bfb5a568 477
478 $field_name_scope{$table_name} ||= {};
479 my $field_name = mk_name(
480 $field->name, '', $field_name_scope{$table_name}, 1
481 );
08d91aad 482 my $field_name_ur = $qf ? $field_name : unreserve($field_name, $table_name );
bfb5a568 483 $field->name($field_name_ur);
484 my $field_comments = $field->comments
485 ? "-- " . $field->comments . "\n "
486 : '';
487
488 my $field_def = $field_comments.qq[$qf$field_name_ur$qf];
489
490 #
491 # Datatype
492 #
493 my @size = $field->size;
494 my $data_type = lc $field->data_type;
495 my %extra = $field->extra;
496 my $list = $extra{'list'} || [];
497 # todo deal with embedded quotes
498 my $commalist = join( ', ', map { qq['$_'] } @$list );
bfb5a568 499
5342f5c1 500 if ($postgres_version >= 8.3 && $field->data_type eq 'enum') {
501 my $type_name = $field->table->name . '_' . $field->name . '_type';
502 $field_def .= ' '. $type_name;
3406fd5b 503 push @$type_defs, "CREATE TYPE $type_name AS ENUM ($commalist)";
504 push @$type_drops, "DROP TYPE IF EXISTS $type_name";
5342f5c1 505 } else {
506 $field_def .= ' '. convert_datatype($field);
507 }
bfb5a568 508
509 #
bc8e2aa1 510 # Default value
bfb5a568 511 #
f39e9c12 512 my $default = $field->default_value;
bfb5a568 513 if ( defined $default ) {
bc8e2aa1 514 SQL::Translator::Producer->_apply_default_value(
515 \$field_def,
516 $default,
517 [
518 'NULL' => \'NULL',
519 'now()' => 'now()',
520 'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
521 ],
522 );
bfb5a568 523 }
524
525 #
526 # Not null constraint
527 #
528 $field_def .= ' NOT NULL' unless $field->is_nullable;
529
530 return $field_def;
531 }
532}
533
bfb5a568 534 sub create_index
535 {
536 my ($index, $options) = @_;
537
538 my $qt = $options->{quote_table_names} ||'';
539 my $qf = $options->{quote_field_names} ||'';
540 my $table_name = $index->table->name;
08d91aad 541# my $table_name_ur = $qt ? unreserve($table_name) : $table_name;
bfb5a568 542
543 my ($index_def, @constraint_defs);
544
bfb5a568 545 my $name = $index->name || '';
546 if ( $name ) {
999859d7 547 $name = next_unused_name($name);
bfb5a568 548 }
549
550 my $type = $index->type || NORMAL;
551 my @fields =
552 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 553 map { $qt ? $_ : unreserve($_, $table_name ) }
bfb5a568 554 $index->fields;
555 next unless @fields;
556
3406fd5b 557 my $def_start = qq[CONSTRAINT "$name" ];
bfb5a568 558 if ( $type eq PRIMARY_KEY ) {
559 push @constraint_defs, "${def_start}PRIMARY KEY ".
560 '(' .$qf . join( $qf. ', '.$qf, @fields ) . $qf . ')';
561 }
562 elsif ( $type eq UNIQUE ) {
563 push @constraint_defs, "${def_start}UNIQUE " .
564 '(' . $qf . join( $qf. ', '.$qf, @fields ) . $qf.')';
565 }
566 elsif ( $type eq NORMAL ) {
567 $index_def =
08d91aad 568 "CREATE INDEX ${qf}${name}${qf} on ${qt}${table_name}${qt} (".
bfb5a568 569 join( ', ', map { qq[$qf$_$qf] } @fields ).
3406fd5b 570 ')'
bfb5a568 571 ;
572 }
573 else {
574 warn "Unknown index type ($type) on table $table_name.\n"
575 if $WARN;
576 }
577
578 return $index_def, \@constraint_defs;
579 }
580
581 sub create_constraint
582 {
583 my ($c, $options) = @_;
584
585 my $qf = $options->{quote_field_names} ||'';
586 my $qt = $options->{quote_table_names} ||'';
587 my $table_name = $c->table->name;
588 my (@constraint_defs, @fks);
589
590 my $name = $c->name || '';
591 if ( $name ) {
999859d7 592 $name = next_unused_name($name);
bfb5a568 593 }
594
595 my @fields =
596 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 597 map { $qt ? $_ : unreserve( $_, $table_name )}
bfb5a568 598 $c->fields;
599
600 my @rfields =
601 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 602 map { $qt ? $_ : unreserve( $_, $table_name )}
bfb5a568 603 $c->reference_fields;
604
605 next if !@fields && $c->type ne CHECK_C;
3406fd5b 606 my $def_start = $name ? qq[CONSTRAINT "$name" ] : '';
bfb5a568 607 if ( $c->type eq PRIMARY_KEY ) {
608 push @constraint_defs, "${def_start}PRIMARY KEY ".
609 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
610 }
611 elsif ( $c->type eq UNIQUE ) {
999859d7 612 $name = next_unused_name($name);
bfb5a568 613 push @constraint_defs, "${def_start}UNIQUE " .
614 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
615 }
616 elsif ( $c->type eq CHECK_C ) {
617 my $expression = $c->expression;
618 push @constraint_defs, "${def_start}CHECK ($expression)";
619 }
620 elsif ( $c->type eq FOREIGN_KEY ) {
621 my $def .= "ALTER TABLE ${qt}${table_name}${qt} ADD FOREIGN KEY (" .
622 join( ', ', map { qq[$qf$_$qf] } @fields ) . ')' .
623 "\n REFERENCES " . $qt . $c->reference_table . $qt;
624
625 if ( @rfields ) {
626 $def .= ' ('.$qf . join( $qf.', '.$qf, @rfields ) . $qf.')';
627 }
628
629 if ( $c->match_type ) {
630 $def .= ' MATCH ' .
631 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
632 }
633
634 if ( $c->on_delete ) {
635 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
636 }
637
638 if ( $c->on_update ) {
639 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
640 }
641
5342f5c1 642 if ( $c->deferrable ) {
643 $def .= ' DEFERRABLE';
644 }
645
3406fd5b 646 push @fks, "$def";
bfb5a568 647 }
648
649 return \@constraint_defs, \@fks;
650 }
bfb5a568 651
652sub convert_datatype
653{
654 my ($field) = @_;
655
656 my @size = $field->size;
657 my $data_type = lc $field->data_type;
658
659 if ( $data_type eq 'enum' ) {
660# my $len = 0;
661# $len = ($len < length($_)) ? length($_) : $len for (@$list);
662# my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
663# push @$constraint_defs,
3406fd5b 664# qq[CONSTRAINT "$chk_name" CHECK ($qf$field_name$qf ].
bfb5a568 665# qq[IN ($commalist))];
666 $data_type = 'character varying';
667 }
668 elsif ( $data_type eq 'set' ) {
669 $data_type = 'character varying';
670 }
671 elsif ( $field->is_auto_increment ) {
672 if ( defined $size[0] && $size[0] > 11 ) {
673 $data_type = 'bigserial';
674 }
675 else {
676 $data_type = 'serial';
677 }
678 undef @size;
679 }
680 else {
681 $data_type = defined $translate{ $data_type } ?
682 $translate{ $data_type } :
683 $data_type;
684 }
685
686 if ( $data_type =~ /timestamp/i ) {
687 if ( defined $size[0] && $size[0] > 6 ) {
688 $size[0] = 6;
689 }
690 }
691
692 if ( $data_type eq 'integer' ) {
693 if ( defined $size[0] && $size[0] > 0) {
694 if ( $size[0] > 10 ) {
695 $data_type = 'bigint';
696 }
697 elsif ( $size[0] < 5 ) {
698 $data_type = 'smallint';
699 }
700 else {
701 $data_type = 'integer';
702 }
703 }
704 else {
705 $data_type = 'integer';
706 }
707 }
e56dabb7 708 my @type_without_size = qw/bigint boolean box bytea cidr circle date inet
709 integer smallint text line lseg macaddr money
710 path point polygon real/;
711 foreach (@type_without_size) {
712 if ( $data_type =~ qr/$_/ ) {
713 undef @size; last;
714 }
715 }
bfb5a568 716
bfb5a568 717 if ( defined $size[0] && $size[0] > 0 ) {
718 $data_type .= '(' . join( ',', @size ) . ')';
719 }
08d91aad 720 elsif (defined $size[0] && $data_type eq 'timestamp' ) {
721 $data_type .= '(' . join( ',', @size ) . ')';
722 }
bfb5a568 723
724
725 return $data_type;
726}
727
728
729sub alter_field
730{
731 my ($from_field, $to_field) = @_;
732
733 die "Can't alter field in another table"
734 if($from_field->table->name ne $to_field->table->name);
735
736 my @out;
3406fd5b 737 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
bfb5a568 738 $to_field->table->name,
739 $to_field->name) if(!$to_field->is_nullable and
740 $from_field->is_nullable);
741
742 my $from_dt = convert_datatype($from_field);
743 my $to_dt = convert_datatype($to_field);
3406fd5b 744 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
bfb5a568 745 $to_field->table->name,
746 $to_field->name,
747 $to_dt) if($to_dt ne $from_dt);
748
3406fd5b 749 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
bfb5a568 750 $to_field->table->name,
751 $from_field->name,
752 $to_field->name) if($from_field->name ne $to_field->name);
753
3406fd5b 754 my $old_default = $from_field->default_value;
755 my $new_default = $to_field->default_value;
756 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
bfb5a568 757 $to_field->table->name,
758 $to_field->name,
3406fd5b 759 $to_field->default_value)
760 if ( defined $new_default &&
761 (!defined $old_default || $old_default ne $new_default) );
bfb5a568 762
763 return wantarray ? @out : join("\n", @out);
bfb5a568 764}
765
3406fd5b 766sub rename_field { alter_field(@_) }
767
bfb5a568 768sub add_field
769{
770 my ($new_field) = @_;
771
3406fd5b 772 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
bfb5a568 773 $new_field->table->name,
774 create_field($new_field));
775 return $out;
776
777}
778
779sub drop_field
780{
781 my ($old_field) = @_;
782
3406fd5b 783 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
bfb5a568 784 $old_field->table->name,
785 $old_field->name);
786
787 return $out;
788}
789
3406fd5b 790sub alter_table {
791 my ($to_table, $options) = @_;
792 my $qt = $options->{quote_table_names} || '';
793 my $out = sprintf('ALTER TABLE %s %s',
794 $qt . $to_table->name . $qt,
795 $options->{alter_table_action});
796 return $out;
797}
798
799sub rename_table {
800 my ($old_table, $new_table, $options) = @_;
801 my $qt = $options->{quote_table_names} || '';
802 $options->{alter_table_action} = "RENAME TO $qt$new_table$qt";
803 return alter_table($old_table, $options);
804}
805
806sub alter_create_index {
807 my ($index, $options) = @_;
808 my $qt = $options->{quote_table_names} || '';
809 my $qf = $options->{quote_field_names} || '';
810 my ($idef, $constraints) = create_index($index, {
811 quote_field_names => $qf,
812 quote_table_names => $qt,
813 table_name => $index->table->name,
814 });
815 return $index->type eq NORMAL ? $idef
816 : sprintf('ALTER TABLE %s ADD %s',
817 $qt . $index->table->name . $qt,
818 join(q{}, @$constraints)
819 );
820}
821
822sub alter_drop_index {
823 my ($index, $options) = @_;
824 my $index_name = $index->name;
825 return "DROP INDEX $index_name";
826}
827
828sub alter_drop_constraint {
829 my ($c, $options) = @_;
830 my $qt = $options->{quote_table_names} || '';
831 my $qc = $options->{quote_field_names} || '';
832 my $out = sprintf('ALTER TABLE %s DROP CONSTRAINT %s',
833 $qt . $c->table->name . $qt,
834 $qc . $c->name . $qc );
835 return $out;
836}
837
838sub alter_create_constraint {
839 my ($index, $options) = @_;
840 my $qt = $options->{quote_table_names} || '';
841 return $index->type eq FOREIGN_KEY ? join(q{}, @{create_constraint(@_)})
842 : join( ' ', 'ALTER TABLE', $qt.$index->table->name.$qt,
843 'ADD', join(q{}, map { @{$_} } create_constraint(@_))
844 );
845}
846
847sub drop_table {
848 my ($table, $options) = @_;
849 my $qt = $options->{quote_table_names} || '';
850 return "DROP TABLE $qt$table$qt CASCADE";
851}
852
f8f0253c 8531;
f8f0253c 854
96844cae 855# -------------------------------------------------------------------
856# Life is full of misery, loneliness, and suffering --
857# and it's all over much too soon.
858# Woody Allen
859# -------------------------------------------------------------------
f8f0253c 860
96844cae 861=pod
f8f0253c 862
20770e44 863=head1 SEE ALSO
864
865SQL::Translator, SQL::Translator::Producer::Oracle.
866
f8f0253c 867=head1 AUTHOR
868
20770e44 869Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
f8f0253c 870
871=cut