File Coverage

blib/lib/SQL/Translator/Producer/PostgreSQL.pm
Criterion Covered Total %
statement 359 398 90.2
branch 161 224 71.8
condition 82 108 75.9
subroutine 36 38 94.7
pod 10 28 35.7
total 648 796 81.4


line stmt bran cond sub pod time code
1             package SQL::Translator::Producer::PostgreSQL;
2              
3             =head1 NAME
4              
5             SQL::Translator::Producer::PostgreSQL - PostgreSQL producer for SQL::Translator
6              
7             =head1 SYNOPSIS
8              
9             my $t = SQL::Translator->new( parser => '...', producer => 'PostgreSQL' );
10             $t->translate;
11              
12             =head1 DESCRIPTION
13              
14             Creates a DDL suitable for PostgreSQL. Very heavily based on the Oracle
15             producer.
16              
17             Now handles PostGIS Geometry and Geography data types on table definitions.
18             Does not yet support PostGIS Views.
19              
20             =cut
21              
22 6     6   2938 use strict;
  6         14  
  6         208  
23 6     6   36 use warnings;
  6         16  
  6         562  
24             our ( $DEBUG, $WARN );
25             our $VERSION = '1.62';
26             $DEBUG = 0 unless defined $DEBUG;
27              
28 6     6   43 use base qw(SQL::Translator::Producer);
  6         16  
  6         1564  
29 6     6   65 use SQL::Translator::Schema::Constants;
  6         13  
  6         556  
30 6     6   1007 use SQL::Translator::Utils qw(debug header_comment parse_dbms_version batch_alter_table_statements normalize_quote_options);
  6         20  
  6         524  
31 6     6   2828 use SQL::Translator::Generator::DDL::PostgreSQL;
  6         23  
  6         205  
32 6     6   50 use Data::Dumper;
  6         17  
  6         339  
33              
34 6     6   44 use constant MAX_ID_LENGTH => 62;
  6         14  
  6         1409  
35              
36             {
37             my ($quoting_generator, $nonquoting_generator);
38             sub _generator {
39 250     250   522 my $options = shift;
40 250 100       907 return $options->{generator} if exists $options->{generator};
41              
42 152 100 66     406 return normalize_quote_options($options)
      66        
43             ? $quoting_generator ||= SQL::Translator::Generator::DDL::PostgreSQL->new
44             : $nonquoting_generator ||= SQL::Translator::Generator::DDL::PostgreSQL->new(
45             quote_chars => [],
46             );
47             }
48             }
49              
50             my ( %translate );
51              
52             BEGIN {
53              
54 6     6   34990 %translate = (
55             #
56             # MySQL types
57             #
58             double => 'double precision',
59             decimal => 'numeric',
60             int => 'integer',
61             mediumint => 'integer',
62             tinyint => 'smallint',
63             char => 'character',
64             varchar => 'character varying',
65             longtext => 'text',
66             mediumtext => 'text',
67             tinytext => 'text',
68             tinyblob => 'bytea',
69             blob => 'bytea',
70             mediumblob => 'bytea',
71             longblob => 'bytea',
72             enum => 'character varying',
73             set => 'character varying',
74             datetime => 'timestamp',
75             year => 'date',
76              
77             #
78             # Oracle types
79             #
80             number => 'integer',
81             varchar2 => 'character varying',
82             long => 'text',
83             clob => 'text',
84              
85             #
86             # Sybase types
87             #
88             comment => 'text',
89              
90             #
91             # MS Access types
92             #
93             memo => 'text',
94             );
95             }
96             my %truncated;
97              
98             =pod
99              
100             =head1 PostgreSQL Create Table Syntax
101              
102             CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
103             { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
104             | table_constraint } [, ... ]
105             )
106             [ INHERITS ( parent_table [, ... ] ) ]
107             [ WITH OIDS | WITHOUT OIDS ]
108              
109             where column_constraint is:
110              
111             [ CONSTRAINT constraint_name ]
112             { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
113             CHECK (expression) |
114             REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
115             [ ON DELETE action ] [ ON UPDATE action ] }
116             [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
117              
118             and table_constraint is:
119              
120             [ CONSTRAINT constraint_name ]
121             { UNIQUE ( column_name [, ... ] ) |
122             PRIMARY KEY ( column_name [, ... ] ) |
123             CHECK ( expression ) |
124             FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
125             [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
126             [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
127              
128             =head1 Create Index Syntax
129              
130             CREATE [ UNIQUE ] INDEX index_name ON table
131             [ USING acc_method ] ( column [ ops_name ] [, ...] )
132             [ WHERE predicate ]
133             CREATE [ UNIQUE ] INDEX index_name ON table
134             [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
135             [ WHERE predicate ]
136              
137             =cut
138              
139             sub produce {
140 6     6 1 18 my $translator = shift;
141 6         28 local $DEBUG = $translator->debug;
142 6         187 local $WARN = $translator->show_warnings;
143 6         157 my $no_comments = $translator->no_comments;
144 6         186 my $add_drop_table = $translator->add_drop_table;
145 6         185 my $schema = $translator->schema;
146 6         198 my $pargs = $translator->producer_args;
147             my $postgres_version = parse_dbms_version(
148 6         45 $pargs->{postgres_version}, 'perl'
149             );
150              
151 6         160 my $generator = _generator({ quote_identifiers => $translator->quote_identifiers });
152              
153 6         41 my @output;
154 6 50       22 push @output, header_comment unless ($no_comments);
155              
156 6         36 my (@table_defs, @fks);
157 6         0 my %type_defs;
158 6         33 for my $table ( $schema->get_tables ) {
159              
160 14         101 my ($table_def, $fks) = create_table($table, {
161             generator => $generator,
162             no_comments => $no_comments,
163             postgres_version => $postgres_version,
164             add_drop_table => $add_drop_table,
165             type_defs => \%type_defs,
166             });
167              
168 14         54 push @table_defs, $table_def;
169 14         48 push @fks, @$fks;
170             }
171              
172 6         44 for my $view ( $schema->get_views ) {
173 3         30 push @table_defs, create_view($view, {
174             postgres_version => $postgres_version,
175             add_drop_view => $add_drop_table,
176             generator => $generator,
177             no_comments => $no_comments,
178             });
179             }
180              
181 6         41 for my $trigger ( $schema->get_triggers ) {
182 8         62 push @table_defs, create_trigger( $trigger, {
183             add_drop_trigger => $add_drop_table,
184             generator => $generator,
185             no_comments => $no_comments,
186             });
187             }
188              
189 6         30 push @output, map { "$_;\n\n" } values %type_defs;
  0         0  
190 6         18 push @output, map { "$_;\n\n" } @table_defs;
  33         113  
191 6 100       27 if ( @fks ) {
192 3 50       41 push @output, "--\n-- Foreign Key Definitions\n--\n\n" unless $no_comments;
193 3         10 push @output, map { "$_;\n\n" } @fks;
  3         14  
194             }
195              
196 6 100       25 if ( $WARN ) {
197 1 50       3 if ( %truncated ) {
198 0         0 warn "Truncated " . keys( %truncated ) . " names:\n";
199 0         0 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
200             }
201             }
202              
203             return wantarray
204             ? @output
205 6 100       679 : join ('', @output);
206             }
207              
208             {
209             my %global_names;
210             sub mk_name {
211 0   0 0 0 0 my $basename = shift || '';
212 0   0     0 my $type = shift || '';
213 0   0     0 my $scope = shift || '';
214 0   0     0 my $critical = shift || '';
215 0         0 my $basename_orig = $basename;
216              
217 0 0       0 my $max_name = $type
218             ? MAX_ID_LENGTH - (length($type) + 1)
219             : MAX_ID_LENGTH;
220 0 0       0 $basename = substr( $basename, 0, $max_name )
221             if length( $basename ) > $max_name;
222 0 0       0 my $name = $type ? "${type}_$basename" : $basename;
223              
224 0 0 0     0 if ( $basename ne $basename_orig and $critical ) {
225 0 0       0 my $show_type = $type ? "+'$type'" : "";
226 0 0       0 warn "Truncating '$basename_orig'$show_type to ", MAX_ID_LENGTH,
227             " character limit to make '$name'\n" if $WARN;
228 0         0 $truncated{ $basename_orig } = $name;
229             }
230              
231 0   0     0 $scope ||= \%global_names;
232 0 0       0 if ( my $prev = $scope->{ $name } ) {
233 0         0 my $name_orig = $name;
234 0         0 $name .= sprintf( "%02d", ++$prev );
235 0 0       0 substr($name, MAX_ID_LENGTH - 3) = "00"
236             if length( $name ) > MAX_ID_LENGTH;
237              
238 0 0       0 warn "The name '$name_orig' has been changed to ",
239             "'$name' to make it unique.\n" if $WARN;
240              
241 0         0 $scope->{ $name_orig }++;
242             }
243              
244 0         0 $scope->{ $name }++;
245 0         0 return $name;
246             }
247             }
248              
249             sub is_geometry {
250 178     178 0 319 my $field = shift;
251 178 100       854 return 1 if $field->data_type eq 'geometry';
252             }
253              
254             sub is_geography {
255 0     0 0 0 my $field = shift;
256 0 0       0 return 1 if $field->data_type eq 'geography';
257             }
258              
259             sub create_table
260             {
261 16     16 1 54 my ($table, $options) = @_;
262              
263 16         50 my $generator = _generator($options);
264 16   100     82 my $no_comments = $options->{no_comments} || 0;
265 16   100     66 my $add_drop_table = $options->{add_drop_table} || 0;
266 16   50     143 my $postgres_version = $options->{postgres_version} || 0;
267 16   100     84 my $type_defs = $options->{type_defs} || {};
268              
269 16 50       381 my $table_name = $table->name or next;
270 16         424 my $table_name_qt = $generator->quote($table_name);
271              
272 16         48 my ( @comments, @field_defs, @index_defs, @constraint_defs, @fks );
273              
274 16 100       60 push @comments, "--\n-- Table: $table_name\n--\n" unless $no_comments;
275              
276 16 100 100     113 if ( !$no_comments and my $comments = $table->comments ) {
277 1         7 $comments =~ s/^/-- /mg;
278 1         18 push @comments, "-- Comments:\n$comments\n--\n";
279             }
280              
281             #
282             # Fields
283             #
284 16         75 for my $field ( $table->get_fields ) {
285 46         299 push @field_defs, create_field($field, {
286             generator => $generator,
287             postgres_version => $postgres_version,
288             type_defs => $type_defs,
289             constraint_defs => \@constraint_defs,
290             });
291             }
292              
293             #
294             # Index Declarations
295             #
296 16         94 for my $index ( $table->get_indices ) {
297 3         54 my ($idef, $constraints) = create_index($index, {
298             generator => $generator,
299             });
300 3 50       22 $idef and push @index_defs, $idef;
301 3         11 push @constraint_defs, @$constraints;
302             }
303              
304             #
305             # Table constraints
306             #
307 16         70 for my $c ( $table->get_constraints ) {
308 20         158 my ($cdefs, $fks) = create_constraint($c, {
309             generator => $generator,
310             });
311 20         69 push @constraint_defs, @$cdefs;
312 20         59 push @fks, @$fks;
313             }
314              
315              
316 16         60 my $create_statement = join("\n", @comments);
317 16 100       64 if ($add_drop_table) {
318 12 50       60 if ($postgres_version >= 8.002) {
319 0         0 $create_statement .= "DROP TABLE IF EXISTS $table_name_qt CASCADE;\n";
320             } else {
321 12         47 $create_statement .= "DROP TABLE $table_name_qt CASCADE;\n";
322             }
323             }
324 16 50       413 my $temporary = $table->extra->{temporary} ? "TEMPORARY " : "";
325             $create_statement .= "CREATE ${temporary}TABLE $table_name_qt (\n" .
326 16         85 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
  61         200  
327             "\n)"
328             ;
329 16 100       62 $create_statement .= @index_defs ? ';' : q{};
330 16 100       90 $create_statement .= ( $create_statement =~ /;$/ ? "\n" : q{} )
331             . join(";\n", @index_defs);
332              
333             #
334             # Geometry
335             #
336 16 100       98 if (my @geometry_columns = grep { is_geometry($_) } $table->get_fields) {
  46         101  
337 1 50       4 $create_statement .= join(";\n", '', map{ drop_geometry_column($_, $options) } @geometry_columns) if $options->{add_drop_table};
  0         0  
338 1         3 $create_statement .= join(";\n", '', map{ add_geometry_column($_, $options) } @geometry_columns);
  1         5  
339             }
340              
341 16         121 return $create_statement, \@fks;
342             }
343              
344             sub create_view {
345 7     7 1 1539 my ($view, $options) = @_;
346 7         25 my $generator = _generator($options);
347 7   100     44 my $postgres_version = $options->{postgres_version} || 0;
348 7         36 my $add_drop_view = $options->{add_drop_view};
349              
350 7         30 my $view_name = $view->name;
351 7         44 debug("PKG: Looking at view '${view_name}'\n");
352              
353 7         19 my $create = '';
354             $create .= "--\n-- View: " . $generator->quote($view_name) . "\n--\n"
355 7 50       26 unless $options->{no_comments};
356 7 100       22 if ($add_drop_view) {
357 5 100       30 if ($postgres_version >= 8.002) {
358 1         6 $create .= "DROP VIEW IF EXISTS " . $generator->quote($view_name) . ";\n";
359             } else {
360 4         21 $create .= "DROP VIEW " . $generator->quote($view_name) . ";\n";
361             }
362             }
363 7         19 $create .= 'CREATE';
364              
365 7         178 my $extra = $view->extra;
366 7 50 66     35 $create .= " TEMPORARY" if exists($extra->{temporary}) && $extra->{temporary};
367 7         29 $create .= " VIEW " . $generator->quote($view_name);
368              
369 7 100       181 if ( my @fields = $view->fields ) {
370 6         22 my $field_list = join ', ', map { $generator->quote($_) } @fields;
  9         32  
371 6         31 $create .= " ( ${field_list} )";
372             }
373              
374 7 50       41 if ( my $sql = $view->sql ) {
375 7         36 $create .= " AS\n ${sql}\n";
376             }
377              
378 7 100       33 if ( $extra->{check_option} ) {
379 1         8 $create .= ' WITH ' . uc $extra->{check_option} . ' CHECK OPTION';
380             }
381              
382 7         46 return $create;
383             }
384              
385             {
386              
387             my %field_name_scope;
388              
389             sub create_field
390             {
391 74     74 1 835 my ($field, $options) = @_;
392              
393 74         189 my $generator = _generator($options);
394 74         1603 my $table_name = $field->table->name;
395 74   100     1603 my $constraint_defs = $options->{constraint_defs} || [];
396 74   100     323 my $postgres_version = $options->{postgres_version} || 0;
397 74   100     287 my $type_defs = $options->{type_defs} || {};
398              
399 74   100     283 $field_name_scope{$table_name} ||= {};
400 74         1548 my $field_name = $field->name;
401 74         1414 my $field_comments = '';
402 74 100       1501 if (my $comments = $field->comments) {
403 4         80 $comments =~ s/(?
404 4         18 $field_comments = "-- $comments\n ";
405             }
406              
407 74         803 my $field_def = $field_comments . $generator->quote($field_name);
408              
409             #
410             # Datatype
411             #
412 74         308 my $data_type = lc $field->data_type;
413 74         1438 my %extra = $field->extra;
414 74   100     365 my $list = $extra{'list'} || [];
415 74         218 my $commalist = join( ', ', map { __PACKAGE__->_quote_string($_) } @$list );
  6         17  
416              
417 74 100 100     281 if ($postgres_version >= 8.003 && $data_type eq 'enum') {
418 2   66     27 my $type_name = $extra{'custom_type_name'} || $field->table->name . '_' . $field->name . '_type';
419 2         24 $field_def .= ' '. $type_name;
420 2         10 my $new_type_def = "DROP TYPE IF EXISTS $type_name CASCADE;\n" .
421             "CREATE TYPE $type_name AS ENUM ($commalist)";
422 2 50       11 if (! exists $type_defs->{$type_name} ) {
    0          
423 2         6 $type_defs->{$type_name} = $new_type_def;
424             } elsif ( $type_defs->{$type_name} ne $new_type_def ) {
425 0         0 die "Attempted to redefine type name '$type_name' as a different type.\n";
426             }
427             } else {
428 72         213 $field_def .= ' '. convert_datatype($field);
429             }
430              
431             #
432             # Default value
433             #
434 74         570 __PACKAGE__->_apply_default_value(
435             $field,
436             \$field_def,
437             [
438             'NULL' => \'NULL',
439             'now()' => 'now()',
440             'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
441             ],
442             );
443              
444             #
445             # Not null constraint
446             #
447 74 100       1712 $field_def .= ' NOT NULL' unless $field->is_nullable;
448              
449             #
450             # Geometry constraints
451             #
452 74 100       3629 if (is_geometry($field)) {
453 3         11 foreach ( create_geometry_constraints($field, $options) ) {
454 9         51 my ($cdefs, $fks) = create_constraint($_, $options);
455 9         32 push @$constraint_defs, @$cdefs;
456 9         38 push @$fks, @$fks;
457             }
458             }
459              
460 74         465 return $field_def;
461             }
462             }
463              
464             sub create_geometry_constraints {
465 7     7 0 19 my ($field, $options) = @_;
466              
467 7         16 my $fname = _generator($options)->quote($field);
468 7         18 my @constraints;
469             push @constraints, SQL::Translator::Schema::Constraint->new(
470             name => "enforce_dims_".$field->name,
471 7         141 expression => "(ST_NDims($fname) = ".$field->extra->{dimensions}.")",
472             table => $field->table,
473             type => CHECK_C,
474             );
475              
476             push @constraints, SQL::Translator::Schema::Constraint->new(
477             name => "enforce_srid_".$field->name,
478 7         352 expression => "(ST_SRID($fname) = ".$field->extra->{srid}.")",
479             table => $field->table,
480             type => CHECK_C,
481             );
482             push @constraints, SQL::Translator::Schema::Constraint->new(
483             name => "enforce_geotype_".$field->name,
484 7         306 expression => "(GeometryType($fname) = ". __PACKAGE__->_quote_string($field->extra->{geometry_type}) ."::text OR $fname IS NULL)",
485             table => $field->table,
486             type => CHECK_C,
487             );
488              
489 7         173 return @constraints;
490             }
491              
492             {
493             my %index_name;
494             sub create_index
495             {
496 12     12 1 2893 my ($index, $options) = @_;
497              
498 12         41 my $generator = _generator($options);
499 12         307 my $table_name = $index->table->name;
500              
501 12         259 my ($index_def, @constraint_defs);
502              
503             my $name
504             = $index->name
505 12   33     260 || join('_', $table_name, 'idx', ++$index_name{ $table_name });
506              
507 12   50     321 my $type = $index->type || NORMAL;
508 12         472 my @fields = $index->fields;
509 12 50       42 return unless @fields;
510              
511 12         31 my $index_using;
512             my $index_where;
513 12         239 for my $opt ( $index->options ) {
514 4 50       15 if ( ref $opt eq 'HASH' ) {
515 4         13 foreach my $key (keys %$opt) {
516 4         8 my $value = $opt->{$key};
517 4 50       11 next unless defined $value;
518 4 100       14 if ( uc($key) eq 'USING' ) {
    50          
519 2         8 $index_using = "USING $value";
520             }
521             elsif ( uc($key) eq 'WHERE' ) {
522 2         7 $index_where = "WHERE $value";
523             }
524             }
525             }
526             }
527              
528 12         53 my $def_start = 'CONSTRAINT ' . $generator->quote($name) . ' ';
529 12 100       45 my $field_names = '(' . join(", ", (map { $_ =~ /\(.*\)/ ? $_ : ( $generator->quote($_) ) } @fields)) . ')';
  16         92  
530 12 50       100 if ( $type eq PRIMARY_KEY ) {
    100          
    50          
531 0         0 push @constraint_defs, "${def_start}PRIMARY KEY ".$field_names;
532             }
533             elsif ( $type eq UNIQUE ) {
534 1         8 push @constraint_defs, "${def_start}UNIQUE " .$field_names;
535             }
536             elsif ( $type eq NORMAL ) {
537             $index_def =
538             'CREATE INDEX ' . $generator->quote($name) . ' on ' . $generator->quote($table_name) . ' ' .
539 11         40 join ' ', grep { defined } $index_using, $field_names, $index_where;
  33         87  
540             }
541             else {
542 0 0       0 warn "Unknown index type ($type) on table $table_name.\n"
543             if $WARN;
544             }
545              
546 12         71 return $index_def, \@constraint_defs;
547             }
548             }
549              
550             sub create_constraint
551             {
552 54     54 1 2376 my ($c, $options) = @_;
553              
554 54         127 my $generator = _generator($options);
555 54         1254 my $table_name = $c->table->name;
556 54         1034 my (@constraint_defs, @fks);
557              
558 54   100     1110 my $name = $c->name || '';
559              
560 54         210 my @fields = grep { defined } $c->fields;
  40         141  
561              
562 54         1107 my @rfields = grep { defined } $c->reference_fields;
  6         31  
563              
564 54 50 66     548 next if !@fields && $c->type ne CHECK_C;
565 54 100       607 my $def_start = $name ? 'CONSTRAINT ' . $generator->quote($name) . ' ' : '';
566 54 100       148 my $field_names = '(' . join(", ", (map { $_ =~ /\(.*\)/ ? $_ : ( $generator->quote($_) ) } @fields)) . ')';
  40         179  
567 54 100       1106 if ( $c->type eq PRIMARY_KEY ) {
    100          
    100          
    100          
568 11         261 push @constraint_defs, "${def_start}PRIMARY KEY ".$field_names;
569             }
570             elsif ( $c->type eq UNIQUE ) {
571 14         320 push @constraint_defs, "${def_start}UNIQUE " .$field_names;
572             }
573             elsif ( $c->type eq CHECK_C ) {
574 18         638 my $expression = $c->expression;
575 18         75 push @constraint_defs, "${def_start}CHECK ($expression)";
576             }
577             elsif ( $c->type eq FOREIGN_KEY ) {
578 6         150 my $def .= "ALTER TABLE " . $generator->quote($table_name) . " ADD ${def_start}FOREIGN KEY $field_names"
579             . "\n REFERENCES " . $generator->quote($c->reference_table);
580              
581 6 50       66 if ( @rfields ) {
582 6         19 $def .= ' (' . join( ', ', map { $generator->quote($_) } @rfields ) . ')';
  6         23  
583             }
584              
585 6 50       134 if ( $c->match_type ) {
586 0 0       0 $def .= ' MATCH ' .
587             ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
588             }
589              
590 6 50       257 if ( $c->on_delete ) {
591 0         0 $def .= ' ON DELETE '. $c->on_delete;
592             }
593              
594 6 50       131 if ( $c->on_update ) {
595 0         0 $def .= ' ON UPDATE '. $c->on_update;
596             }
597              
598 6 50       120 if ( $c->deferrable ) {
599 6         57 $def .= ' DEFERRABLE';
600             }
601              
602 6         22 push @fks, "$def";
603             }
604              
605 54         360 return \@constraint_defs, \@fks;
606             }
607              
608             sub create_trigger {
609 8     8 1 25 my ($trigger,$options) = @_;
610 8         27 my $generator = _generator($options);
611              
612 8         24 my @statements;
613              
614             push @statements, sprintf( 'DROP TRIGGER IF EXISTS %s', $generator->quote($trigger->name) )
615 8 50       62 if $options->{add_drop_trigger};
616              
617 8   100     171 my $scope = $trigger->scope || '';
618 8 100       200 $scope = " FOR EACH $scope" if $scope;
619              
620             push @statements, sprintf(
621             'CREATE TRIGGER %s %s %s ON %s%s %s',
622             $generator->quote($trigger->name),
623             $trigger->perform_action_when,
624 8         50 join( ' OR ', @{ $trigger->database_events } ),
  8         338  
625             $generator->quote($trigger->on_table),
626             $scope,
627             $trigger->action,
628             );
629              
630 8         51 return @statements;
631             }
632              
633             sub convert_datatype
634             {
635 112     112 0 218 my ($field) = @_;
636              
637 112         2217 my @size = $field->size;
638 112         1195 my $data_type = lc $field->data_type;
639 112         297 my $array = $data_type =~ s/\[\]$//;
640              
641 112 50       2477 if ( $data_type eq 'enum' ) {
    50          
    100          
642             # my $len = 0;
643             # $len = ($len < length($_)) ? length($_) : $len for (@$list);
644             # my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
645             # push @$constraint_defs,
646             # 'CONSTRAINT "$chk_name" CHECK (' . $generator->quote(field_name) .
647             # qq[IN ($commalist))];
648 0         0 $data_type = 'character varying';
649             }
650             elsif ( $data_type eq 'set' ) {
651 0         0 $data_type = 'character varying';
652             }
653             elsif ( $field->is_auto_increment ) {
654 13 100 66     209 if ( (defined $size[0] && $size[0] > 11) or $data_type eq 'bigint' ) {
      100        
655 2         5 $data_type = 'bigserial';
656             }
657             else {
658 11         34 $data_type = 'serial';
659             }
660 13         34 undef @size;
661             }
662             else {
663             $data_type = defined $translate{ lc $data_type } ?
664 99 100       1086 $translate{ lc $data_type } :
665             $data_type;
666             }
667              
668 112 100 100     642 if ( $data_type =~ /^time/i || $data_type =~ /^interval/i ) {
669 11 100 66     86 if ( defined $size[0] && $size[0] > 6 ) {
670 1         3 $size[0] = 6;
671             }
672             }
673              
674 112 100       293 if ( $data_type eq 'integer' ) {
675 15 100 66     100 if ( defined $size[0] && $size[0] > 0) {
676 13 100       43 if ( $size[0] > 10 ) {
    50          
677 10         21 $data_type = 'bigint';
678             }
679             elsif ( $size[0] < 5 ) {
680 0         0 $data_type = 'smallint';
681             }
682             else {
683 3         11 $data_type = 'integer';
684             }
685             }
686             else {
687 2         6 $data_type = 'integer';
688             }
689             }
690              
691 112         214 my $type_with_size = join('|',
692             'bit', 'varbit', 'character', 'bit varying', 'character varying',
693             'time', 'timestamp', 'interval', 'numeric', 'float'
694             );
695              
696 112 100       1203 if ( $data_type !~ /$type_with_size/ ) {
697 48         120 @size = ();
698             }
699              
700 112 100 100     777 if (defined $size[0] && $size[0] > 0 && $data_type =~ /^time/i ) {
    100 100        
      100        
701 5         45 $data_type =~ s/^(time.*?)( with.*)?$/$1($size[0])/;
702 5 50       22 $data_type .= $2 if(defined $2);
703             } elsif ( defined $size[0] && $size[0] > 0 ) {
704 51         237 $data_type .= '(' . join( ',', @size ) . ')';
705             }
706 112 100       247 if($array)
707             {
708 1         2 $data_type .= '[]';
709             }
710              
711             #
712             # Geography
713             #
714 112 100       239 if($data_type eq 'geography'){
715 1         20 $data_type .= '('.$field->extra->{geography_type}.','. $field->extra->{srid} .')'
716             }
717              
718 112         381 return $data_type;
719             }
720              
721              
722             sub alter_field
723             {
724 20     20 1 3030 my ($from_field, $to_field, $options) = @_;
725              
726 20 50       453 die "Can't alter field in another table"
727             if($from_field->table->name ne $to_field->table->name);
728              
729 20         393 my $generator = _generator($options);
730 20         45 my @out;
731              
732             # drop geometry column and constraints
733 20 100       46 push @out,
734             drop_geometry_column($from_field, $options),
735             drop_geometry_constraints($from_field, $options),
736             if is_geometry($from_field);
737              
738             # it's necessary to start with rename column cause this would affect
739             # all of the following statements which would be broken if do the
740             # rename later
741             # BUT: drop geometry is done before the rename, cause it work's on the
742             # $from_field directly
743 20 100       441 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
744             map($generator->quote($_),
745             $to_field->table->name,
746             $from_field->name,
747             $to_field->name,
748             ),
749             )
750             if($from_field->name ne $to_field->name);
751              
752 20 100 100     687 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
753             map($generator->quote($_),
754             $to_field->table->name,
755             $to_field->name
756             ),
757             )
758             if(!$to_field->is_nullable and $from_field->is_nullable);
759              
760 20 100 100     765 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL',
761             map($generator->quote($_),
762             $to_field->table->name,
763             $to_field->name
764             ),
765             )
766             if (!$from_field->is_nullable and $to_field->is_nullable);
767              
768              
769 20         504 my $from_dt = convert_datatype($from_field);
770 20         53 my $to_dt = convert_datatype($to_field);
771 20 100       252 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
772             map($generator->quote($_),
773             $to_field->table->name,
774             $to_field->name
775             ),
776             $to_dt,
777             )
778             if($to_dt ne $from_dt);
779              
780 20         82 my $old_default = $from_field->default_value;
781 20         45 my $new_default = $to_field->default_value;
782 20         43 my $default_value = $to_field->default_value;
783              
784             # fixes bug where output like this was created:
785             # ALTER TABLE users ALTER COLUMN column SET DEFAULT ThisIsUnescaped;
786 20 100 100     112 if(ref $default_value eq "SCALAR" ) {
    100          
787 1         2 $default_value = $$default_value;
788             } elsif( defined $default_value && $to_dt =~ /^(character|text|timestamp|date)/xsmi ) {
789 3         14 $default_value = __PACKAGE__->_quote_string($default_value);
790             }
791              
792 20 100 100     182 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
      100        
793             map($generator->quote($_),
794             $to_field->table->name,
795             $to_field->name,
796             ),
797             $default_value,
798             )
799             if ( defined $new_default &&
800             (!defined $old_default || $old_default ne $new_default) );
801              
802             # fixes bug where removing the DEFAULT statement of a column
803             # would result in no change
804              
805 20 100 100     117 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
806             map($generator->quote($_),
807             $to_field->table->name,
808             $to_field->name,
809             ),
810             )
811             if ( !defined $new_default && defined $old_default );
812              
813             # add geometry column and constraints
814 20 100       54 push @out,
815             add_geometry_column($to_field, $options),
816             add_geometry_constraints($to_field, $options),
817             if is_geometry($to_field);
818              
819 20 100       117 return wantarray ? @out : join(";\n", @out);
820             }
821              
822 2     2 0 6 sub rename_field { alter_field(@_) }
823              
824             sub add_field
825             {
826 6     6 1 69 my ($new_field,$options) = @_;
827              
828 6         105 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
829             _generator($options)->quote($new_field->table->name),
830             create_field($new_field, $options));
831 6 100       61 $out .= ";\n".add_geometry_column($new_field, $options)
832             . ";\n".add_geometry_constraints($new_field, $options)
833             if is_geometry($new_field);
834 6         37 return $out;
835              
836             }
837              
838             sub drop_field
839             {
840 4     4 1 1667 my ($old_field, $options) = @_;
841              
842 4         18 my $generator = _generator($options);
843              
844 4         110 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
845             $generator->quote($old_field->table->name),
846             $generator->quote($old_field->name));
847 4 100       24 $out .= ";\n".drop_geometry_column($old_field, $options)
848             if is_geometry($old_field);
849 4         28 return $out;
850             }
851              
852             sub add_geometry_column {
853 5     5 0 999 my ($field, $options) = @_;
854              
855             return sprintf(
856             "INSERT INTO geometry_columns VALUES (%s,%s,%s,%s,%s,%s,%s)",
857             map(__PACKAGE__->_quote_string($_),
858             '',
859             $field->table->schema->name,
860             $options->{table} ? $options->{table} : $field->table->name,
861             $field->name,
862             $field->extra->{dimensions},
863             $field->extra->{srid},
864             $field->extra->{geometry_type},
865 5 100       116 ),
866             );
867             }
868              
869             sub drop_geometry_column {
870 4     4 0 14 my ($field) = @_;
871              
872 4         84 return sprintf(
873             "DELETE FROM geometry_columns WHERE f_table_schema = %s AND f_table_name = %s AND f_geometry_column = %s",
874             map(__PACKAGE__->_quote_string($_),
875             $field->table->schema->name,
876             $field->table->name,
877             $field->name,
878             ),
879             );
880             }
881              
882             sub add_geometry_constraints {
883 3     3 0 677 my ($field, $options) = @_;
884              
885 3         11 return join(";\n", map { alter_create_constraint($_, $options) }
  9         26  
886             create_geometry_constraints($field, $options));
887             }
888              
889             sub drop_geometry_constraints {
890 1     1 0 4 my ($field, $options) = @_;
891              
892 1         4 return join(";\n", map { alter_drop_constraint($_, $options) }
  3         13  
893             create_geometry_constraints($field, $options));
894              
895             }
896              
897             sub alter_table {
898 3     3 0 10 my ($to_table, $options) = @_;
899 3         11 my $generator = _generator($options);
900             my $out = sprintf('ALTER TABLE %s %s',
901             $generator->quote($to_table->name),
902 3         65 $options->{alter_table_action});
903 3 100       21 $out .= ";\n".$options->{geometry_changes} if $options->{geometry_changes};
904 3         27 return $out;
905             }
906              
907             sub rename_table {
908 3     3 0 940 my ($old_table, $new_table, $options) = @_;
909 3         11 my $generator = _generator($options);
910 3         17 $options->{alter_table_action} = "RENAME TO " . $generator->quote($new_table);
911              
912             my @geometry_changes = map {
913             drop_geometry_column($_, $options),
914 1         5 add_geometry_column($_, { %{$options}, table => $new_table }),
  1         7  
915 3         43 } grep { is_geometry($_) } $old_table->get_fields;
  4         13  
916              
917 3 100       18 $options->{geometry_changes} = join (";\n",@geometry_changes) if @geometry_changes;
918              
919 3         15 return alter_table($old_table, $options);
920             }
921              
922             sub alter_create_index {
923 1     1 0 4 my ($index, $options) = @_;
924 1         4 my $generator = _generator($options);
925 1         20 my ($idef, $constraints) = create_index($index, $options);
926 1 50       56 return $index->type eq NORMAL ? $idef
927             : sprintf('ALTER TABLE %s ADD %s',
928             $generator->quote($index->table->name),
929             join(q{}, @$constraints)
930             );
931             }
932              
933             sub alter_drop_index {
934 1     1 0 4 my ($index, $options) = @_;
935 1         5 return 'DROP INDEX '. _generator($options)->quote($index->name);
936             }
937              
938             sub alter_drop_constraint {
939 11     11 0 4461 my ($c, $options) = @_;
940 11         32 my $generator = _generator($options);
941              
942             # attention: Postgres has a very special naming structure for naming
943             # foreign keys and primary keys. It names them using the name of the
944             # table as prefix and fkey or pkey as suffix, concatenated by an underscore
945 11         32 my $c_name;
946 11 100       271 if( $c->name ) {
    100          
    50          
947             # Already has a name, just use it
948 9         170 $c_name = $c->name;
949             } elsif ( $c->type eq FOREIGN_KEY ) {
950             # Doesn't have a name, and is foreign key, append '_fkey'
951 1         42 $c_name = $c->table->name . '_' . ($c->fields)[0] . '_fkey';
952             } elsif ( $c->type eq PRIMARY_KEY ) {
953             # Doesn't have a name, and is primary key, append '_pkey'
954 1         39 $c_name = $c->table->name . '_pkey';
955             }
956              
957             return sprintf(
958             'ALTER TABLE %s DROP CONSTRAINT %s',
959 11         230 map { $generator->quote($_) } $c->table->name, $c_name,
  22         278  
960             );
961             }
962              
963             sub alter_create_constraint {
964 14     14 0 36 my ($index, $options) = @_;
965 14         38 my $generator = _generator($options);
966 14         51 my ($defs, $fks) = create_constraint(@_);
967              
968             # return if there are no constraint definitions so we don't run
969             # into output like this:
970             # ALTER TABLE users ADD ;
971              
972 14 50 66     25 return unless(@{$defs} || @{$fks});
  14         44  
  1         4  
973 1         33 return $index->type eq FOREIGN_KEY ? join(q{}, @{$fks})
974             : join( ' ', 'ALTER TABLE', $generator->quote($index->table->name),
975 14 100       266 'ADD', join(q{}, @{$defs}, @{$fks})
  13         29  
  13         104  
976             );
977             }
978              
979             sub drop_table {
980 3     3 0 48 my ($table, $options) = @_;
981 3         11 my $generator = _generator($options);
982 3         16 my $out = "DROP TABLE " . $generator->quote($table) . " CASCADE";
983              
984 3         41 my @geometry_drops = map { drop_geometry_column($_); } grep { is_geometry($_) } $table->get_fields;
  1         4  
  4         13  
985              
986 3 100       14 $out .= join(";\n", '', @geometry_drops) if @geometry_drops;
987 3         15 return $out;
988             }
989              
990             sub batch_alter_table {
991 12     12 0 402 my ( $table, $diff_hash, $options ) = @_;
992              
993             # as long as we're not renaming the table we don't need to be here
994 12 100       18 if ( @{$diff_hash->{rename_table}} == 0 ) {
  12         42  
995 10         35 return batch_alter_table_statements($diff_hash, $options);
996             }
997              
998             # first we need to perform drops which are on old table
999 2         10 my @sql = batch_alter_table_statements($diff_hash, $options, qw(
1000             alter_drop_constraint
1001             alter_drop_index
1002             drop_field
1003             ));
1004              
1005             # next comes the rename_table
1006 2         6 my $old_table = $diff_hash->{rename_table}[0][0];
1007 2         10 push @sql, rename_table( $old_table, $table, $options );
1008              
1009             # for alter_field (and so also rename_field) we need to make sure old
1010             # field has table name set to new table otherwise calling alter_field dies
1011             $diff_hash->{alter_field} =
1012 2 0       7 [map { $_->[0]->table($table) && $_ } @{$diff_hash->{alter_field}}];
  0         0  
  2         6  
1013             $diff_hash->{rename_field} =
1014 2 0       6 [map { $_->[0]->table($table) && $_ } @{$diff_hash->{rename_field}}];
  0         0  
  2         7  
1015              
1016             # now add everything else
1017 2         22 push @sql, batch_alter_table_statements($diff_hash, $options, qw(
1018             add_field
1019             alter_field
1020             rename_field
1021             alter_create_index
1022             alter_create_constraint
1023             alter_table
1024             ));
1025              
1026 2         19 return @sql;
1027             }
1028              
1029             1;
1030              
1031             # -------------------------------------------------------------------
1032             # Life is full of misery, loneliness, and suffering --
1033             # and it's all over much too soon.
1034             # Woody Allen
1035             # -------------------------------------------------------------------
1036              
1037             =pod
1038              
1039             =head1 SEE ALSO
1040              
1041             SQL::Translator, SQL::Translator::Producer::Oracle.
1042              
1043             =head1 AUTHOR
1044              
1045             Ken Youens-Clark Ekclark@cpan.orgE.
1046              
1047             =cut