added triggers and procedures
[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
f7abfd61 219 my (@trigger_defs);
220 foreach my $trigger ( $schema->get_triggers ) {
221 push @trigger_defs, create_trigger($trigger);
222 }
223
224
bf75adec 225 push @output, map { "$_;\n\n" } @table_defs;
08d91aad 226 if ( @fks ) {
bf75adec 227 push @output, "--\n-- Foreign Key Definitions\n--\n\n" unless $no_comments;
228 push @output, map { "$_;\n\n" } @fks;
08d91aad 229 }
021dbce8 230
f7abfd61 231 if (@trigger_defs) {
232 push @output, "--\n-- Triggers \n--\n\n" unless $no_comments;
233 push @output, map { "$_;\n\n" } @trigger_defs;
234 }
235
da8e499e 236 if ( $WARN ) {
237 if ( %truncated ) {
238 warn "Truncated " . keys( %truncated ) . " names:\n";
239 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
240 }
241
242 if ( %unreserve ) {
243 warn "Encounted " . keys( %unreserve ) .
244 " unsafe names in schema (reserved or invalid):\n";
245 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
246 }
f8f0253c 247 }
248
f7abfd61 249 foreach my $procedure ( $schema->get_procedures ) {
250 my (@comments, $procedure_name);
251
252 $procedure_name = $procedure->name();
253 push @comments,
254 "--\n-- Procedure: $procedure_name\n--" unless $no_comments;
255
256 # text of procedure already has the 'create procedure' stuff
257 # so there is no need to do anything fancy. However, we should
258 # think about doing fancy stuff with granting permissions and
259 # so on.
260
261 push (@output, join("\n\n",
262 @comments,
263 $procedure->sql(),
264 ));
265 }
266
267
bf75adec 268 return wantarray
269 ? @output
270 : join ('', @output);
f8f0253c 271}
272
96844cae 273# -------------------------------------------------------------------
274sub mk_name {
0c43e0a1 275 my $basename = shift || '';
276 my $type = shift || '';
277 my $scope = shift || '';
278 my $critical = shift || '';
96844cae 279 my $basename_orig = $basename;
bfb5a568 280# my $max_id_length = 62;
2ad4c2c8 281 my $max_name = $type
282 ? $max_id_length - (length($type) + 1)
283 : $max_id_length;
96844cae 284 $basename = substr( $basename, 0, $max_name )
285 if length( $basename ) > $max_name;
286 my $name = $type ? "${type}_$basename" : $basename;
287
288 if ( $basename ne $basename_orig and $critical ) {
289 my $show_type = $type ? "+'$type'" : "";
290 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
291 "character limit to make '$name'\n" if $WARN;
292 $truncated{ $basename_orig } = $name;
293 }
294
295 $scope ||= \%global_names;
296 if ( my $prev = $scope->{ $name } ) {
297 my $name_orig = $name;
298 $name .= sprintf( "%02d", ++$prev );
299 substr($name, $max_id_length - 3) = "00"
300 if length( $name ) > $max_id_length;
301
302 warn "The name '$name_orig' has been changed to ",
303 "'$name' to make it unique.\n" if $WARN;
304
305 $scope->{ $name_orig }++;
f8f0253c 306 }
96844cae 307
308 $scope->{ $name }++;
309 return $name;
310}
311
312# -------------------------------------------------------------------
313sub unreserve {
0c43e0a1 314 my $name = shift || '';
315 my $schema_obj_name = shift || '';
316
96844cae 317 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
318
319 # also trap fields that don't begin with a letter
bfb5a568 320 return $name if (!$reserved{ uc $name }) && $name =~ /^[a-z]/i;
96844cae 321
322 if ( $schema_obj_name ) {
323 ++$unreserve{"$schema_obj_name.$name"};
324 }
325 else {
326 ++$unreserve{"$name (table name)"};
327 }
328
329 my $unreserve = sprintf '%s_', $name;
330 return $unreserve.$suffix;
f8f0253c 331}
332
50840472 333# -------------------------------------------------------------------
334sub next_unused_name {
999859d7 335 my $name = shift || '';
336 if ( !defined( $used_names{$name} ) ) {
50840472 337 $used_names{$name} = $name;
338 return $name;
339 }
999859d7 340
50840472 341 my $i = 2;
999859d7 342 while ( defined( $used_names{ $name . $i } ) ) {
50840472 343 ++$i;
344 }
345 $name .= $i;
346 $used_names{$name} = $name;
347 return $name;
348}
349
999859d7 350
bfb5a568 351sub create_table
352{
353 my ($table, $options) = @_;
354
355 my $qt = $options->{quote_table_names} || '';
356 my $qf = $options->{quote_field_names} || '';
357 my $no_comments = $options->{no_comments} || 0;
358 my $add_drop_table = $options->{add_drop_table} || 0;
5342f5c1 359 my $postgres_version = $options->{postgres_version} || 0;
bfb5a568 360
3406fd5b 361 my $table_name = $table->name or next;
3406fd5b 362 my ( $fql_tbl_name ) = ( $table_name =~ s/\W(.*)$// ) ? $1 : q{};
363 my $table_name_ur = $qt ? $table_name
364 : $fql_tbl_name ? join('.', $table_name, unreserve($fql_tbl_name))
365 : unreserve($table_name);
bfb5a568 366 $table->name($table_name_ur);
367
f7abfd61 368 # print STDERR "$table_name table_name\n";
5342f5c1 369 my ( @comments, @field_defs, @sequence_defs, @constraint_defs, @type_defs, @type_drops, @fks );
bfb5a568 370
7ed7402c 371 push @comments, "--\n-- Table: $table_name_ur\n--\n" unless $no_comments;
bfb5a568 372
373 if ( $table->comments and !$no_comments ){
374 my $c = "-- Comments: \n-- ";
375 $c .= join "\n-- ", $table->comments;
7ed7402c 376 $c .= "\n--\n";
bfb5a568 377 push @comments, $c;
378 }
379
380 #
381 # Fields
382 #
383 my %field_name_scope;
384 for my $field ( $table->get_fields ) {
385 push @field_defs, create_field($field, { quote_table_names => $qt,
386 quote_field_names => $qf,
387 table_name => $table_name_ur,
5342f5c1 388 postgres_version => $postgres_version,
389 type_defs => \@type_defs,
390 type_drops => \@type_drops,
bfb5a568 391 constraint_defs => \@constraint_defs,});
392 }
393
394 #
395 # Index Declarations
396 #
397 my @index_defs = ();
398 # my $idx_name_default;
399 for my $index ( $table->get_indices ) {
400 my ($idef, $constraints) = create_index($index,
401 {
402 quote_field_names => $qf,
403 quote_table_names => $qt,
404 table_name => $table_name,
405 });
7ed7402c 406 $idef and push @index_defs, $idef;
bfb5a568 407 push @constraint_defs, @$constraints;
408 }
409
410 #
411 # Table constraints
412 #
413 my $c_name_default;
414 for my $c ( $table->get_constraints ) {
415 my ($cdefs, $fks) = create_constraint($c,
416 {
417 quote_field_names => $qf,
418 quote_table_names => $qt,
419 table_name => $table_name,
420 });
421 push @constraint_defs, @$cdefs;
422 push @fks, @$fks;
423 }
424
3e98f7d9 425
426 my $temporary = "";
427
428 if(exists $table->{extra}{temporary}) {
429 $temporary = $table->{extra}{temporary} ? "TEMPORARY " : "";
430 }
431
bfb5a568 432 my $create_statement;
433 $create_statement = join("\n", @comments);
5342f5c1 434 if ($add_drop_table) {
435 if ($postgres_version >= 8.2) {
436 $create_statement .= qq[DROP TABLE IF EXISTS $qt$table_name_ur$qt CASCADE;\n];
938464ee 437 $create_statement .= join (";\n", @type_drops) . ";\n"
438 if $postgres_version >= 8.3 && scalar @type_drops;
5342f5c1 439 } else {
440 $create_statement .= qq[DROP TABLE $qt$table_name_ur$qt CASCADE;\n];
441 }
442 }
938464ee 443 $create_statement .= join(";\n", @type_defs) . ";\n"
444 if $postgres_version >= 8.3 && scalar @type_defs;
3e98f7d9 445 $create_statement .= qq[CREATE ${temporary}TABLE $qt$table_name_ur$qt (\n].
bfb5a568 446 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
3406fd5b 447 "\n)"
bfb5a568 448 ;
3406fd5b 449 $create_statement .= @index_defs ? ';' : q{};
450 $create_statement .= ( $create_statement =~ /;$/ ? "\n" : q{} )
451 . join(";\n", @index_defs);
bfb5a568 452
08d91aad 453 return $create_statement, \@fks;
bfb5a568 454}
455
296c2701 456sub create_view {
457 my ($view, $options) = @_;
458 my $qt = $options->{quote_table_names} || '';
459 my $qf = $options->{quote_field_names} || '';
a25ac5d2 460 my $add_drop_view = $options->{add_drop_view};
296c2701 461
462 my $view_name = $view->name;
463 debug("PKG: Looking at view '${view_name}'\n");
464
465 my $create = '';
466 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n"
467 unless $options->{no_comments};
a25ac5d2 468 $create .= "DROP VIEW ${qt}${view_name}${qt};\n" if $add_drop_view;
296c2701 469 $create .= 'CREATE';
296c2701 470
471 my $extra = $view->extra;
472 $create .= " TEMPORARY" if exists($extra->{temporary}) && $extra->{temporary};
473 $create .= " VIEW ${qt}${view_name}${qt}";
474
475 if ( my @fields = $view->fields ) {
476 my $field_list = join ', ', map { "${qf}${_}${qf}" } @fields;
477 $create .= " ( ${field_list} )";
478 }
479
480 if ( my $sql = $view->sql ) {
f59b2c0e 481 $create .= " AS\n ${sql}\n";
296c2701 482 }
483
484 if ( $extra->{check_option} ) {
485 $create .= ' WITH ' . uc $extra->{check_option} . ' CHECK OPTION';
486 }
487
296c2701 488 return $create;
489}
490
bfb5a568 491{
492
493 my %field_name_scope;
494
495 sub create_field
496 {
497 my ($field, $options) = @_;
498
499 my $qt = $options->{quote_table_names} || '';
500 my $qf = $options->{quote_field_names} || '';
501 my $table_name = $field->table->name;
502 my $constraint_defs = $options->{constraint_defs} || [];
5342f5c1 503 my $postgres_version = $options->{postgres_version} || 0;
504 my $type_defs = $options->{type_defs} || [];
505 my $type_drops = $options->{type_drops} || [];
bfb5a568 506
507 $field_name_scope{$table_name} ||= {};
912e67a1 508 my $field_name = $field->name;
08d91aad 509 my $field_name_ur = $qf ? $field_name : unreserve($field_name, $table_name );
bfb5a568 510 $field->name($field_name_ur);
511 my $field_comments = $field->comments
512 ? "-- " . $field->comments . "\n "
513 : '';
514
515 my $field_def = $field_comments.qq[$qf$field_name_ur$qf];
516
517 #
518 # Datatype
519 #
520 my @size = $field->size;
521 my $data_type = lc $field->data_type;
522 my %extra = $field->extra;
523 my $list = $extra{'list'} || [];
524 # todo deal with embedded quotes
525 my $commalist = join( ', ', map { qq['$_'] } @$list );
bfb5a568 526
5342f5c1 527 if ($postgres_version >= 8.3 && $field->data_type eq 'enum') {
528 my $type_name = $field->table->name . '_' . $field->name . '_type';
529 $field_def .= ' '. $type_name;
3406fd5b 530 push @$type_defs, "CREATE TYPE $type_name AS ENUM ($commalist)";
531 push @$type_drops, "DROP TYPE IF EXISTS $type_name";
5342f5c1 532 } else {
533 $field_def .= ' '. convert_datatype($field);
534 }
bfb5a568 535
536 #
bc8e2aa1 537 # Default value
bfb5a568 538 #
f39e9c12 539 my $default = $field->default_value;
bfb5a568 540 if ( defined $default ) {
bc8e2aa1 541 SQL::Translator::Producer->_apply_default_value(
542 \$field_def,
543 $default,
544 [
545 'NULL' => \'NULL',
546 'now()' => 'now()',
547 'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
548 ],
549 );
bfb5a568 550 }
551
552 #
553 # Not null constraint
554 #
555 $field_def .= ' NOT NULL' unless $field->is_nullable;
556
557 return $field_def;
558 }
559}
560
f7abfd61 561sub create_index {
562 my ($index, $options) = @_;
bfb5a568 563
f7abfd61 564 my $qt = $options->{quote_table_names} ||'';
565 my $qf = $options->{quote_field_names} ||'';
566 my $table_name = $index->table->name;
567 # my $table_name_ur = $qt ? unreserve($table_name) : $table_name;
bfb5a568 568
f7abfd61 569 my ($index_def, @constraint_defs);
bfb5a568 570
f7abfd61 571 my $name = $index->name || '';
572 if ( $name ) {
573 $name = next_unused_name($name);
574 }
bfb5a568 575
f7abfd61 576 my $type = $index->type || NORMAL;
577 my @fields =
578 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 579 map { $qt ? $_ : unreserve($_, $table_name ) }
f7abfd61 580 $index->fields;
581 next unless @fields;
582
583 my $def_start = qq[CONSTRAINT "$name" ];
584 if ( $type eq PRIMARY_KEY ) {
585 push @constraint_defs, "${def_start}PRIMARY KEY ".
586 '(' .$qf . join( $qf. ', '.$qf, @fields ) . $qf . ')';
587 } elsif ( $type eq UNIQUE ) {
588 push @constraint_defs, "${def_start}UNIQUE " .
589 '(' . $qf . join( $qf. ', '.$qf, @fields ) . $qf.')';
590 } elsif ( $type eq NORMAL ) {
591 $index_def =
592 "CREATE INDEX ${qf}${name}${qf} on ${qt}${table_name}${qt} (".
593 join( ', ', map { qq[$qf$_$qf] } @fields ).
594 ')'
bfb5a568 595 ;
f7abfd61 596 } else {
597 warn "Unknown index type ($type) on table $table_name.\n"
598 if $WARN;
bfb5a568 599 }
600
f7abfd61 601 return $index_def, \@constraint_defs;
602}
bfb5a568 603
f7abfd61 604sub create_constraint {
605 my ($c, $options) = @_;
bfb5a568 606
f7abfd61 607 my $qf = $options->{quote_field_names} ||'';
608 my $qt = $options->{quote_table_names} ||'';
609 my $table_name = $c->table->name;
610 my (@constraint_defs, @fks);
bfb5a568 611
f7abfd61 612 my $name = $c->name || '';
613 if ( $name ) {
614 $name = next_unused_name($name);
615 }
bfb5a568 616
f7abfd61 617 my @fields =
618 map { $_ =~ s/\(.+\)//; $_ }
08d91aad 619 map { $qt ? $_ : unreserve( $_, $table_name )}
f7abfd61 620 $c->fields;
bfb5a568 621
f7abfd61 622 my @rfields =
623 map { $_ =~ s/\(.+\)//; $_ }
624 map { $qt ? $_ : unreserve( $_, $table_name )}
625 $c->reference_fields;
626
627 next if !@fields && $c->type ne CHECK_C;
628 my $def_start = $name ? qq[CONSTRAINT "$name" ] : '';
629 if ( $c->type eq PRIMARY_KEY ) {
630 push @constraint_defs, "${def_start}PRIMARY KEY ".
631 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
632 } elsif ( $c->type eq UNIQUE ) {
633 $name = next_unused_name($name);
634 push @constraint_defs, "${def_start}UNIQUE " .
635 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
636 } elsif ( $c->type eq CHECK_C ) {
637 my $expression = $c->expression;
638 push @constraint_defs, "${def_start}CHECK ($expression)";
639 } elsif ( $c->type eq FOREIGN_KEY ) {
640 my $def .= "ALTER TABLE ${qt}${table_name}${qt} ADD FOREIGN KEY (" .
641 join( ', ', map { qq[$qf$_$qf] } @fields ) . ')' .
642 "\n REFERENCES " . $qt . $c->reference_table . $qt;
643
644 if ( @rfields ) {
645 $def .= ' ('.$qf . join( $qf.', '.$qf, @rfields ) . $qf.')';
646 }
647
648 if ( $c->match_type ) {
649 $def .= ' MATCH ' .
650 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
651 }
652
653 if ( $c->on_delete ) {
654 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
655 }
656
657 if ( $c->on_update ) {
658 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
659 }
660
661 if ( $c->deferrable ) {
662 $def .= ' DEFERRABLE';
663 }
664
665 push @fks, "$def";
666 }
bfb5a568 667
f7abfd61 668 return \@constraint_defs, \@fks;
669}
bfb5a568 670
bfb5a568 671
f7abfd61 672sub create_trigger {
673 my ($trigger) = @_;
674 # CREATE TRIGGER tree_change_trig BEFORE DELETE or INSERT or UPDATE ON type FOR EACH ROW EXECUTE PROCEDURE type_tree_change();
675 my $db_events = join ' or ', $trigger->database_events;
676 my $out = sprintf('CREATE TRIGGER %s %s %s ON %s',
677 $trigger->name,
678 $trigger->perform_action_when || 'AFTER',
679 $db_events,
680 $trigger->table->name,
681 $trigger->action );
bfb5a568 682
f7abfd61 683 return $out;
684}
5342f5c1 685
bfb5a568 686
bfb5a568 687
688sub convert_datatype
689{
690 my ($field) = @_;
691
692 my @size = $field->size;
693 my $data_type = lc $field->data_type;
694
695 if ( $data_type eq 'enum' ) {
696# my $len = 0;
697# $len = ($len < length($_)) ? length($_) : $len for (@$list);
698# my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
699# push @$constraint_defs,
3406fd5b 700# qq[CONSTRAINT "$chk_name" CHECK ($qf$field_name$qf ].
bfb5a568 701# qq[IN ($commalist))];
702 $data_type = 'character varying';
703 }
704 elsif ( $data_type eq 'set' ) {
705 $data_type = 'character varying';
706 }
707 elsif ( $field->is_auto_increment ) {
708 if ( defined $size[0] && $size[0] > 11 ) {
709 $data_type = 'bigserial';
710 }
711 else {
712 $data_type = 'serial';
713 }
714 undef @size;
715 }
716 else {
717 $data_type = defined $translate{ $data_type } ?
718 $translate{ $data_type } :
719 $data_type;
720 }
721
ad258776 722 if ( $data_type =~ /^time/i || $data_type =~ /^interval/i ) {
bfb5a568 723 if ( defined $size[0] && $size[0] > 6 ) {
724 $size[0] = 6;
725 }
726 }
727
728 if ( $data_type eq 'integer' ) {
729 if ( defined $size[0] && $size[0] > 0) {
730 if ( $size[0] > 10 ) {
731 $data_type = 'bigint';
732 }
733 elsif ( $size[0] < 5 ) {
734 $data_type = 'smallint';
735 }
736 else {
737 $data_type = 'integer';
738 }
739 }
740 else {
741 $data_type = 'integer';
742 }
743 }
e56dabb7 744 my @type_without_size = qw/bigint boolean box bytea cidr circle date inet
745 integer smallint text line lseg macaddr money
746 path point polygon real/;
747 foreach (@type_without_size) {
748 if ( $data_type =~ qr/$_/ ) {
749 undef @size; last;
750 }
751 }
bfb5a568 752
ad258776 753 if (defined $size[0] && $size[0] > 0 && $data_type =~ /^time/i ) {
754 $data_type =~ s/^(time.*?)( with.*)?$/$1($size[0])/;
755 $data_type .= $2 if(defined $2);
756 } elsif ( defined $size[0] && $size[0] > 0 ) {
757 $data_type .= '(' . join( ',', @size ) . ')';
08d91aad 758 }
ad258776 759
bfb5a568 760
761
762 return $data_type;
763}
764
765
766sub alter_field
767{
768 my ($from_field, $to_field) = @_;
769
770 die "Can't alter field in another table"
771 if($from_field->table->name ne $to_field->table->name);
772
773 my @out;
3406fd5b 774 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
bfb5a568 775 $to_field->table->name,
776 $to_field->name) if(!$to_field->is_nullable and
777 $from_field->is_nullable);
778
90726ffd 779 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL',
780 $to_field->table->name,
781 $to_field->name)
782 if ( !$from_field->is_nullable and $to_field->is_nullable );
783
784
bfb5a568 785 my $from_dt = convert_datatype($from_field);
786 my $to_dt = convert_datatype($to_field);
3406fd5b 787 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
bfb5a568 788 $to_field->table->name,
789 $to_field->name,
790 $to_dt) if($to_dt ne $from_dt);
791
3406fd5b 792 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
bfb5a568 793 $to_field->table->name,
794 $from_field->name,
795 $to_field->name) if($from_field->name ne $to_field->name);
796
3406fd5b 797 my $old_default = $from_field->default_value;
798 my $new_default = $to_field->default_value;
90726ffd 799 my $default_value = $to_field->default_value;
800
801 # fixes bug where output like this was created:
802 # ALTER TABLE users ALTER COLUMN column SET DEFAULT ThisIsUnescaped;
803 if(ref $default_value eq "SCALAR" ) {
804 $default_value = $$default_value;
805 } elsif( defined $default_value && $to_dt =~ /^(character|text)/xsmi ) {
806 $default_value =~ s/'/''/xsmg;
807 $default_value = q(') . $default_value . q(');
808 }
809
3406fd5b 810 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
bfb5a568 811 $to_field->table->name,
812 $to_field->name,
90726ffd 813 $default_value)
3406fd5b 814 if ( defined $new_default &&
815 (!defined $old_default || $old_default ne $new_default) );
bfb5a568 816
90726ffd 817 # fixes bug where removing the DEFAULT statement of a column
818 # would result in no change
819
820 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
821 $to_field->table->name,
822 $to_field->name)
823 if ( !defined $new_default && defined $old_default );
824
825
bfb5a568 826 return wantarray ? @out : join("\n", @out);
bfb5a568 827}
828
3406fd5b 829sub rename_field { alter_field(@_) }
830
bfb5a568 831sub add_field
832{
833 my ($new_field) = @_;
834
3406fd5b 835 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
bfb5a568 836 $new_field->table->name,
837 create_field($new_field));
838 return $out;
839
840}
841
842sub drop_field
843{
844 my ($old_field) = @_;
845
3406fd5b 846 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
bfb5a568 847 $old_field->table->name,
848 $old_field->name);
849
850 return $out;
851}
852
3406fd5b 853sub alter_table {
854 my ($to_table, $options) = @_;
855 my $qt = $options->{quote_table_names} || '';
856 my $out = sprintf('ALTER TABLE %s %s',
857 $qt . $to_table->name . $qt,
858 $options->{alter_table_action});
859 return $out;
860}
861
862sub rename_table {
863 my ($old_table, $new_table, $options) = @_;
864 my $qt = $options->{quote_table_names} || '';
865 $options->{alter_table_action} = "RENAME TO $qt$new_table$qt";
866 return alter_table($old_table, $options);
867}
868
869sub alter_create_index {
870 my ($index, $options) = @_;
871 my $qt = $options->{quote_table_names} || '';
872 my $qf = $options->{quote_field_names} || '';
873 my ($idef, $constraints) = create_index($index, {
874 quote_field_names => $qf,
875 quote_table_names => $qt,
876 table_name => $index->table->name,
877 });
878 return $index->type eq NORMAL ? $idef
879 : sprintf('ALTER TABLE %s ADD %s',
880 $qt . $index->table->name . $qt,
881 join(q{}, @$constraints)
882 );
883}
884
885sub alter_drop_index {
886 my ($index, $options) = @_;
887 my $index_name = $index->name;
888 return "DROP INDEX $index_name";
889}
890
891sub alter_drop_constraint {
892 my ($c, $options) = @_;
893 my $qt = $options->{quote_table_names} || '';
894 my $qc = $options->{quote_field_names} || '';
895 my $out = sprintf('ALTER TABLE %s DROP CONSTRAINT %s',
896 $qt . $c->table->name . $qt,
897 $qc . $c->name . $qc );
898 return $out;
899}
900
901sub alter_create_constraint {
902 my ($index, $options) = @_;
903 my $qt = $options->{quote_table_names} || '';
90726ffd 904 my ($defs, $fks) = create_constraint(@_);
905
906 # return if there are no constraint definitions so we don't run
907 # into output like this:
908 # ALTER TABLE users ADD ;
909
910 return unless(@{$defs} || @{$fks});
911 return $index->type eq FOREIGN_KEY ? join(q{}, @{$fks})
3406fd5b 912 : join( ' ', 'ALTER TABLE', $qt.$index->table->name.$qt,
90726ffd 913 'ADD', join(q{}, @{$defs}, @{$fks})
3406fd5b 914 );
915}
916
917sub drop_table {
918 my ($table, $options) = @_;
919 my $qt = $options->{quote_table_names} || '';
920 return "DROP TABLE $qt$table$qt CASCADE";
921}
922
f8f0253c 9231;
f8f0253c 924
96844cae 925# -------------------------------------------------------------------
926# Life is full of misery, loneliness, and suffering --
927# and it's all over much too soon.
928# Woody Allen
929# -------------------------------------------------------------------
f8f0253c 930
96844cae 931=pod
f8f0253c 932
20770e44 933=head1 SEE ALSO
934
935SQL::Translator, SQL::Translator::Producer::Oracle.
936
f8f0253c 937=head1 AUTHOR
938
20770e44 939Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
f8f0253c 940
941=cut