1 package SQL::Translator::Producer::PostgreSQL;
5 SQL::Translator::Producer::PostgreSQL - PostgreSQL producer for SQL::Translator
9 my $t = SQL::Translator->new( parser => '...', producer => 'PostgreSQL' );
14 Creates a DDL suitable for PostgreSQL. Very heavily based on the Oracle
17 Now handles PostGIS Geometry and Geography data types on table definitions.
18 Does not yet support PostGIS Views.
24 use vars qw[ $DEBUG $WARN $VERSION %used_names ];
26 $DEBUG = 0 unless defined $DEBUG;
28 use base qw(SQL::Translator::Producer);
29 use SQL::Translator::Schema::Constants;
30 use SQL::Translator::Utils qw(debug header_comment parse_dbms_version);
33 my ( %translate, %index_name );
47 mediumint => 'integer',
48 smallint => 'smallint',
49 tinyint => 'smallint',
51 varchar => 'character varying',
58 mediumblob => 'bytea',
60 enum => 'character varying',
61 set => 'character varying',
63 datetime => 'timestamp',
65 timestamp => 'timestamp',
73 varchar2 => 'character varying',
83 varchar => 'character varying',
84 datetime => 'timestamp',
89 tinyint => 'smallint',
95 my %reserved = map { $_, 1 } qw[
96 ALL ANALYSE ANALYZE AND ANY AS ASC
98 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
99 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
100 DEFAULT DEFERRABLE DESC DISTINCT DO
102 FALSE FOR FOREIGN FREEZE FROM FULL
104 ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL
105 JOIN LEADING LEFT LIKE LIMIT
106 NATURAL NEW NOT NOTNULL NULL
107 OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
108 PRIMARY PUBLIC REFERENCES RIGHT
109 SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
110 UNION UNIQUE USER USING VERBOSE WHEN WHERE
113 # my $max_id_length = 62;
114 my %used_identifiers = ();
121 =head1 PostgreSQL Create Table Syntax
123 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
124 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
125 | table_constraint } [, ... ]
127 [ INHERITS ( parent_table [, ... ] ) ]
128 [ WITH OIDS | WITHOUT OIDS ]
130 where column_constraint is:
132 [ CONSTRAINT constraint_name ]
133 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
135 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
136 [ ON DELETE action ] [ ON UPDATE action ] }
137 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
139 and table_constraint is:
141 [ CONSTRAINT constraint_name ]
142 { UNIQUE ( column_name [, ... ] ) |
143 PRIMARY KEY ( column_name [, ... ] ) |
144 CHECK ( expression ) |
145 FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
146 [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
147 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
149 =head1 Create Index Syntax
151 CREATE [ UNIQUE ] INDEX index_name ON table
152 [ USING acc_method ] ( column [ ops_name ] [, ...] )
154 CREATE [ UNIQUE ] INDEX index_name ON table
155 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
160 # -------------------------------------------------------------------
162 my $translator = shift;
163 local $DEBUG = $translator->debug;
164 local $WARN = $translator->show_warnings;
165 my $no_comments = $translator->no_comments;
166 my $add_drop_table = $translator->add_drop_table;
167 my $schema = $translator->schema;
168 my $pargs = $translator->producer_args;
169 my $postgres_version = parse_dbms_version(
170 $pargs->{postgres_version}, 'perl'
173 my $qt = $translator->quote_table_names ? q{"} : q{};
174 my $qf = $translator->quote_field_names ? q{"} : q{};
177 push @output, header_comment unless ($no_comments);
179 my (@table_defs, @fks);
181 for my $table ( $schema->get_tables ) {
183 my ($table_def, $fks) = create_table($table, {
184 quote_table_names => $qt,
185 quote_field_names => $qf,
186 no_comments => $no_comments,
187 postgres_version => $postgres_version,
188 add_drop_table => $add_drop_table,
189 type_defs => \%type_defs,
192 push @table_defs, $table_def;
196 for my $view ( $schema->get_views ) {
197 push @table_defs, create_view($view, {
198 add_drop_view => $add_drop_table,
199 quote_table_names => $qt,
200 quote_field_names => $qf,
201 no_comments => $no_comments,
205 push @output, map { "$_;\n\n" } values %type_defs;
206 push @output, map { "$_;\n\n" } @table_defs;
208 push @output, "--\n-- Foreign Key Definitions\n--\n\n" unless $no_comments;
209 push @output, map { "$_;\n\n" } @fks;
214 warn "Truncated " . keys( %truncated ) . " names:\n";
215 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
219 warn "Encounted " . keys( %unreserve ) .
220 " unsafe names in schema (reserved or invalid):\n";
221 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
227 : join ('', @output);
230 # -------------------------------------------------------------------
232 my $basename = shift || '';
233 my $type = shift || '';
234 my $scope = shift || '';
235 my $critical = shift || '';
236 my $basename_orig = $basename;
237 # my $max_id_length = 62;
239 ? $max_id_length - (length($type) + 1)
241 $basename = substr( $basename, 0, $max_name )
242 if length( $basename ) > $max_name;
243 my $name = $type ? "${type}_$basename" : $basename;
245 if ( $basename ne $basename_orig and $critical ) {
246 my $show_type = $type ? "+'$type'" : "";
247 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
248 "character limit to make '$name'\n" if $WARN;
249 $truncated{ $basename_orig } = $name;
252 $scope ||= \%global_names;
253 if ( my $prev = $scope->{ $name } ) {
254 my $name_orig = $name;
255 $name .= sprintf( "%02d", ++$prev );
256 substr($name, $max_id_length - 3) = "00"
257 if length( $name ) > $max_id_length;
259 warn "The name '$name_orig' has been changed to ",
260 "'$name' to make it unique.\n" if $WARN;
262 $scope->{ $name_orig }++;
269 # -------------------------------------------------------------------
271 my $name = shift || '';
272 my $schema_obj_name = shift || '';
274 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
276 # also trap fields that don't begin with a letter
277 return $name if (!$reserved{ uc $name }) && $name =~ /^[a-z]/i;
279 if ( $schema_obj_name ) {
280 ++$unreserve{"$schema_obj_name.$name"};
283 ++$unreserve{"$name (table name)"};
286 my $unreserve = sprintf '%s_', $name;
287 return $unreserve.$suffix;
290 # -------------------------------------------------------------------
291 sub next_unused_name {
292 my $orig_name = shift or return;
293 my $name = $orig_name;
295 my $suffix_gen = sub {
297 return ++$suffix ? '' : $suffix;
301 $name = $orig_name . $suffix_gen->();
302 last if $used_names{ $name }++;
311 return 1 if $field->data_type eq 'geometry';
317 return 1 if $field->data_type eq 'geography';
322 my ($table, $options) = @_;
324 my $qt = $options->{quote_table_names} || '';
325 my $qf = $options->{quote_field_names} || '';
326 my $no_comments = $options->{no_comments} || 0;
327 my $add_drop_table = $options->{add_drop_table} || 0;
328 my $postgres_version = $options->{postgres_version} || 0;
329 my $type_defs = $options->{type_defs} || {};
331 my $table_name = $table->name or next;
332 my ( $fql_tbl_name ) = ( $table_name =~ s/\W(.*)$// ) ? $1 : q{};
333 my $table_name_ur = $qt ? $table_name
334 : $fql_tbl_name ? join('.', $table_name, unreserve($fql_tbl_name))
335 : unreserve($table_name);
336 $table->name($table_name_ur);
338 # print STDERR "$table_name table_name\n";
339 my ( @comments, @field_defs, @sequence_defs, @constraint_defs, @fks );
341 push @comments, "--\n-- Table: $table_name_ur\n--\n" unless $no_comments;
343 if ( $table->comments and !$no_comments ){
344 my $c = "-- Comments: \n-- ";
345 $c .= join "\n-- ", $table->comments;
353 my %field_name_scope;
354 for my $field ( $table->get_fields ) {
355 push @field_defs, create_field($field, { quote_table_names => $qt,
356 quote_field_names => $qf,
357 table_name => $table_name_ur,
358 postgres_version => $postgres_version,
359 type_defs => $type_defs,
360 constraint_defs => \@constraint_defs,});
367 # my $idx_name_default;
368 for my $index ( $table->get_indices ) {
369 my ($idef, $constraints) = create_index($index,
371 quote_field_names => $qf,
372 quote_table_names => $qt,
373 table_name => $table_name,
375 $idef and push @index_defs, $idef;
376 push @constraint_defs, @$constraints;
383 for my $c ( $table->get_constraints ) {
384 my ($cdefs, $fks) = create_constraint($c,
386 quote_field_names => $qf,
387 quote_table_names => $qt,
388 table_name => $table_name,
390 push @constraint_defs, @$cdefs;
397 if(exists $table->{extra}{temporary}) {
398 $temporary = $table->{extra}{temporary} ? "TEMPORARY " : "";
401 my $create_statement;
402 $create_statement = join("\n", @comments);
403 if ($add_drop_table) {
404 if ($postgres_version >= 8.002) {
405 $create_statement .= qq[DROP TABLE IF EXISTS $qt$table_name_ur$qt CASCADE;\n];
407 $create_statement .= qq[DROP TABLE $qt$table_name_ur$qt CASCADE;\n];
410 $create_statement .= qq[CREATE ${temporary}TABLE $qt$table_name_ur$qt (\n].
411 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
414 $create_statement .= @index_defs ? ';' : q{};
415 $create_statement .= ( $create_statement =~ /;$/ ? "\n" : q{} )
416 . join(";\n", @index_defs);
421 if(grep { is_geometry($_) } $table->get_fields){
422 $create_statement .= ";";
423 my @geometry_columns;
424 foreach my $col ($table->get_fields) { push(@geometry_columns,$col) if is_geometry($col); }
425 $create_statement .= "\n".join("\n", map{ drop_geometry_column($_) } @geometry_columns) if $options->{add_drop_table};
426 $create_statement .= "\n".join("\n", map{ add_geometry_column($_) } @geometry_columns);
429 return $create_statement, \@fks;
433 my ($view, $options) = @_;
434 my $qt = $options->{quote_table_names} || '';
435 my $qf = $options->{quote_field_names} || '';
436 my $add_drop_view = $options->{add_drop_view};
438 my $view_name = $view->name;
439 debug("PKG: Looking at view '${view_name}'\n");
442 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n"
443 unless $options->{no_comments};
444 $create .= "DROP VIEW ${qt}${view_name}${qt};\n" if $add_drop_view;
447 my $extra = $view->extra;
448 $create .= " TEMPORARY" if exists($extra->{temporary}) && $extra->{temporary};
449 $create .= " VIEW ${qt}${view_name}${qt}";
451 if ( my @fields = $view->fields ) {
452 my $field_list = join ', ', map { "${qf}${_}${qf}" } @fields;
453 $create .= " ( ${field_list} )";
456 if ( my $sql = $view->sql ) {
457 $create .= " AS\n ${sql}\n";
460 if ( $extra->{check_option} ) {
461 $create .= ' WITH ' . uc $extra->{check_option} . ' CHECK OPTION';
469 my %field_name_scope;
473 my ($field, $options) = @_;
475 my $qt = $options->{quote_table_names} || '';
476 my $qf = $options->{quote_field_names} || '';
477 my $table_name = $field->table->name;
478 my $constraint_defs = $options->{constraint_defs} || [];
479 my $postgres_version = $options->{postgres_version} || 0;
480 my $type_defs = $options->{type_defs} || {};
482 $field_name_scope{$table_name} ||= {};
483 my $field_name = $field->name;
484 my $field_name_ur = $qf ? $field_name : unreserve($field_name, $table_name );
485 $field->name($field_name_ur);
486 my $field_comments = $field->comments
487 ? "-- " . $field->comments . "\n "
490 my $field_def = $field_comments.qq[$qf$field_name_ur$qf];
495 my @size = $field->size;
496 my $data_type = lc $field->data_type;
497 my %extra = $field->extra;
498 my $list = $extra{'list'} || [];
499 # todo deal with embedded quotes
500 my $commalist = join( ', ', map { qq['$_'] } @$list );
502 if ($postgres_version >= 8.003 && $field->data_type eq 'enum') {
503 my $type_name = $extra{'custom_type_name'} || $field->table->name . '_' . $field->name . '_type';
504 $field_def .= ' '. $type_name;
505 my $new_type_def = "DROP TYPE IF EXISTS $type_name CASCADE;\n" .
506 "CREATE TYPE $type_name AS ENUM ($commalist)";
507 if (! exists $type_defs->{$type_name} ) {
508 $type_defs->{$type_name} = $new_type_def;
509 } elsif ( $type_defs->{$type_name} ne $new_type_def ) {
510 die "Attempted to redefine type name '$type_name' as a different type.\n";
513 $field_def .= ' '. convert_datatype($field);
519 SQL::Translator::Producer->_apply_default_value(
525 'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
530 # Not null constraint
532 $field_def .= ' NOT NULL' unless $field->is_nullable;
535 # Geometry constraints
537 if(is_geometry($field)){
538 foreach ( create_geometry_constraints($field) ) {
539 my ($cdefs, $fks) = create_constraint($_,
541 quote_field_names => $qf,
542 quote_table_names => $qt,
543 table_name => $table_name,
545 push @$constraint_defs, @$cdefs;
554 sub create_geometry_constraints{
558 push @constraints, SQL::Translator::Schema::Constraint->new(
559 name => "enforce_dims_".$field->name,
560 expression => "(st_ndims($field) = ".$field->{extra}{dimensions}.")",
561 table => $field->table,
565 push @constraints, SQL::Translator::Schema::Constraint->new(
566 name => "enforce_srid_".$field->name,
567 expression => "(st_srid($field) = ".$field->{extra}{srid}.")",
568 table => $field->table,
571 push @constraints, SQL::Translator::Schema::Constraint->new(
572 name => "enforce_geotype_".$field->name,
573 expression => "(geometrytype($field) = '".$field->{extra}{geometry_type}."'::text OR $field IS NULL)",
574 table => $field->table,
583 my ($index, $options) = @_;
585 my $qt = $options->{quote_table_names} ||'';
586 my $qf = $options->{quote_field_names} ||'';
587 my $table_name = $index->table->name;
588 # my $table_name_ur = $qt ? unreserve($table_name) : $table_name;
590 my ($index_def, @constraint_defs);
592 my $name = next_unused_name(
594 || join('_', $table_name, 'idx', ++$index_name{ $table_name })
597 my $type = $index->type || NORMAL;
599 map { $_ =~ s/\(.+\)//; $_ }
600 map { $qt ? $_ : unreserve($_, $table_name ) }
604 my $def_start = qq[CONSTRAINT "$name" ];
605 if ( $type eq PRIMARY_KEY ) {
606 push @constraint_defs, "${def_start}PRIMARY KEY ".
607 '(' .$qf . join( $qf. ', '.$qf, @fields ) . $qf . ')';
609 elsif ( $type eq UNIQUE ) {
610 push @constraint_defs, "${def_start}UNIQUE " .
611 '(' . $qf . join( $qf. ', '.$qf, @fields ) . $qf.')';
613 elsif ( $type eq NORMAL ) {
615 "CREATE INDEX ${qf}${name}${qf} on ${qt}${table_name}${qt} (".
616 join( ', ', map { qq[$qf$_$qf] } @fields ).
621 warn "Unknown index type ($type) on table $table_name.\n"
625 return $index_def, \@constraint_defs;
628 sub create_constraint
630 my ($c, $options) = @_;
632 my $qf = $options->{quote_field_names} ||'';
633 my $qt = $options->{quote_table_names} ||'';
634 my $table_name = $c->table->name;
635 my (@constraint_defs, @fks);
637 my $name = $c->name || '';
639 $name = next_unused_name($name);
643 map { $_ =~ s/\(.+\)//; $_ }
644 map { $qt ? $_ : unreserve( $_, $table_name )}
648 map { $_ =~ s/\(.+\)//; $_ }
649 map { $qt ? $_ : unreserve( $_, $table_name )}
650 $c->reference_fields;
652 next if !@fields && $c->type ne CHECK_C;
653 my $def_start = $name ? qq[CONSTRAINT "$name" ] : '';
654 if ( $c->type eq PRIMARY_KEY ) {
655 push @constraint_defs, "${def_start}PRIMARY KEY ".
656 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
658 elsif ( $c->type eq UNIQUE ) {
659 $name = next_unused_name($name);
660 push @constraint_defs, "${def_start}UNIQUE " .
661 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
663 elsif ( $c->type eq CHECK_C ) {
664 my $expression = $c->expression;
665 push @constraint_defs, "${def_start}CHECK ($expression)";
667 elsif ( $c->type eq FOREIGN_KEY ) {
668 my $def .= "ALTER TABLE ${qt}${table_name}${qt} ADD FOREIGN KEY (" .
669 join( ', ', map { qq[$qf$_$qf] } @fields ) . ')' .
670 "\n REFERENCES " . $qt . $c->reference_table . $qt;
673 $def .= ' ('.$qf . join( $qf.', '.$qf, @rfields ) . $qf.')';
676 if ( $c->match_type ) {
678 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
681 if ( $c->on_delete ) {
682 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
685 if ( $c->on_update ) {
686 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
689 if ( $c->deferrable ) {
690 $def .= ' DEFERRABLE';
696 return \@constraint_defs, \@fks;
703 my @size = $field->size;
704 my $data_type = lc $field->data_type;
706 if ( $data_type eq 'enum' ) {
708 # $len = ($len < length($_)) ? length($_) : $len for (@$list);
709 # my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
710 # push @$constraint_defs,
711 # qq[CONSTRAINT "$chk_name" CHECK ($qf$field_name$qf ].
712 # qq[IN ($commalist))];
713 $data_type = 'character varying';
715 elsif ( $data_type eq 'set' ) {
716 $data_type = 'character varying';
718 elsif ( $field->is_auto_increment ) {
719 if ( defined $size[0] && $size[0] > 11 ) {
720 $data_type = 'bigserial';
723 $data_type = 'serial';
728 $data_type = defined $translate{ $data_type } ?
729 $translate{ $data_type } :
733 if ( $data_type =~ /^time/i || $data_type =~ /^interval/i ) {
734 if ( defined $size[0] && $size[0] > 6 ) {
739 if ( $data_type eq 'integer' ) {
740 if ( defined $size[0] && $size[0] > 0) {
741 if ( $size[0] > 10 ) {
742 $data_type = 'bigint';
744 elsif ( $size[0] < 5 ) {
745 $data_type = 'smallint';
748 $data_type = 'integer';
752 $data_type = 'integer';
756 my $type_with_size = join('|',
757 'bit', 'varbit', 'character', 'bit varying', 'character varying',
758 'time', 'timestamp', 'interval', 'numeric'
761 if ( $data_type !~ /$type_with_size/ ) {
765 if (defined $size[0] && $size[0] > 0 && $data_type =~ /^time/i ) {
766 $data_type =~ s/^(time.*?)( with.*)?$/$1($size[0])/;
767 $data_type .= $2 if(defined $2);
768 } elsif ( defined $size[0] && $size[0] > 0 ) {
769 $data_type .= '(' . join( ',', @size ) . ')';
775 if($data_type eq 'geography'){
776 $data_type .= '('.$field->{extra}{geography_type}.','. $field->{extra}{srid} .')'
785 my ($from_field, $to_field) = @_;
787 die "Can't alter field in another table"
788 if($from_field->table->name ne $to_field->table->name);
792 # drop geometry column and constraints
793 push @out, drop_geometry_column($from_field) if is_geometry($from_field);
794 push @out, drop_geometry_constraints($from_field) if is_geometry($from_field);
796 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
797 $to_field->table->name,
798 $to_field->name) if(!$to_field->is_nullable and
799 $from_field->is_nullable);
801 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL',
802 $to_field->table->name,
804 if ( !$from_field->is_nullable and $to_field->is_nullable );
807 my $from_dt = convert_datatype($from_field);
808 my $to_dt = convert_datatype($to_field);
809 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
810 $to_field->table->name,
812 $to_dt) if($to_dt ne $from_dt);
814 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
815 $to_field->table->name,
817 $to_field->name) if($from_field->name ne $to_field->name);
819 my $old_default = $from_field->default_value;
820 my $new_default = $to_field->default_value;
821 my $default_value = $to_field->default_value;
823 # fixes bug where output like this was created:
824 # ALTER TABLE users ALTER COLUMN column SET DEFAULT ThisIsUnescaped;
825 if(ref $default_value eq "SCALAR" ) {
826 $default_value = $$default_value;
827 } elsif( defined $default_value && $to_dt =~ /^(character|text)/xsmi ) {
828 $default_value =~ s/'/''/xsmg;
829 $default_value = q(') . $default_value . q(');
832 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
833 $to_field->table->name,
836 if ( defined $new_default &&
837 (!defined $old_default || $old_default ne $new_default) );
839 # fixes bug where removing the DEFAULT statement of a column
840 # would result in no change
842 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
843 $to_field->table->name,
845 if ( !defined $new_default && defined $old_default );
847 # add geometry column and contraints
848 push @out, add_geometry_column($to_field) if is_geometry($to_field);
849 push @out, add_geometry_constraints($to_field) if is_geometry($to_field);
851 return wantarray ? @out : join("\n", @out);
854 sub rename_field { alter_field(@_) }
858 my ($new_field) = @_;
860 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
861 $new_field->table->name,
862 create_field($new_field));
863 $out .= "\n".add_geometry_column($new_field) if is_geometry($new_field);
864 $out .= "\n".add_geometry_constraints($new_field) if is_geometry($new_field);
871 my ($old_field) = @_;
873 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
874 $old_field->table->name,
876 $out .= "\n".drop_geometry_column($old_field) if is_geometry($old_field);
880 sub add_geometry_column{
881 my ($field,$options) = @_;
883 my $out = sprintf("INSERT INTO geometry_columns VALUES ('%s','%s','%s','%s','%s','%s','%s')",
885 $field->table->schema->name,
886 $options->{table} ? $options->{table} : $field->table->name,
888 $field->{extra}{dimensions},
889 $field->{extra}{srid},
890 $field->{extra}{geometry_type});
894 sub drop_geometry_column
898 my $out = sprintf("DELETE FROM geometry_columns WHERE f_table_schema = '%s' AND f_table_name = '%s' AND f_geometry_column = '%s'",
899 $field->table->schema->name,
905 sub add_geometry_constraints{
908 my @constraints = create_geometry_constraints($field);
910 my $out = join("\n", map { alter_create_constraint($_); } @constraints);
915 sub drop_geometry_constraints{
918 my @constraints = create_geometry_constraints($field);
920 my $out = join("\n", map { alter_drop_constraint($_); } @constraints);
926 my ($to_table, $options) = @_;
927 my $qt = $options->{quote_table_names} || '';
928 my $out = sprintf('ALTER TABLE %s %s',
929 $qt . $to_table->name . $qt,
930 $options->{alter_table_action});
931 $out .= "\n".$options->{geometry_changes} if $options->{geometry_changes};
936 my ($old_table, $new_table, $options) = @_;
937 my $qt = $options->{quote_table_names} || '';
938 $options->{alter_table_action} = "RENAME TO $qt$new_table$qt";
940 my @geometry_changes;
941 push @geometry_changes, map { drop_geometry_column($_); } grep { is_geometry($_) } $old_table->get_fields;
942 push @geometry_changes, map { add_geometry_column($_, { table => $new_table }); } grep { is_geometry($_) } $old_table->get_fields;
944 $options->{geometry_changes} = join ("\n",@geometry_changes) if scalar(@geometry_changes);
946 return alter_table($old_table, $options);
949 sub alter_create_index {
950 my ($index, $options) = @_;
951 my $qt = $options->{quote_table_names} || '';
952 my $qf = $options->{quote_field_names} || '';
953 my ($idef, $constraints) = create_index($index, {
954 quote_field_names => $qf,
955 quote_table_names => $qt,
956 table_name => $index->table->name,
958 return $index->type eq NORMAL ? $idef
959 : sprintf('ALTER TABLE %s ADD %s',
960 $qt . $index->table->name . $qt,
961 join(q{}, @$constraints)
965 sub alter_drop_index {
966 my ($index, $options) = @_;
967 my $index_name = $index->name;
968 return "DROP INDEX $index_name";
971 sub alter_drop_constraint {
972 my ($c, $options) = @_;
973 my $qt = $options->{quote_table_names} || '';
974 my $qc = $options->{quote_field_names} || '';
975 my $out = sprintf('ALTER TABLE %s DROP CONSTRAINT %s',
976 $qt . $c->table->name . $qt,
977 $qc . $c->name . $qc );
981 sub alter_create_constraint {
982 my ($index, $options) = @_;
983 my $qt = $options->{quote_table_names} || '';
984 my ($defs, $fks) = create_constraint(@_);
986 # return if there are no constraint definitions so we don't run
987 # into output like this:
988 # ALTER TABLE users ADD ;
990 return unless(@{$defs} || @{$fks});
991 return $index->type eq FOREIGN_KEY ? join(q{}, @{$fks})
992 : join( ' ', 'ALTER TABLE', $qt.$index->table->name.$qt,
993 'ADD', join(q{}, @{$defs}, @{$fks})
998 my ($table, $options) = @_;
999 my $qt = $options->{quote_table_names} || '';
1000 my $out = "DROP TABLE $qt$table$qt CASCADE";
1002 my @geometry_drops = map { drop_geometry_column($_); } grep { is_geometry($_) } $table->get_fields;
1004 $out .= "\n".join("\n",@geometry_drops) if scalar(@geometry_drops);
1010 # -------------------------------------------------------------------
1011 # Life is full of misery, loneliness, and suffering --
1012 # and it's all over much too soon.
1014 # -------------------------------------------------------------------
1020 SQL::Translator, SQL::Translator::Producer::Oracle.
1024 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.