1 package SQL::Translator::Producer::PostgreSQL;
3 # -------------------------------------------------------------------
4 # Copyright (C) 2002-2009 SQLFairy Authors
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.
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.
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
19 # -------------------------------------------------------------------
23 SQL::Translator::Producer::PostgreSQL - PostgreSQL producer for SQL::Translator
27 my $t = SQL::Translator->new( parser => '...', producer => 'PostgreSQL' );
32 Creates a DDL suitable for PostgreSQL. Very heavily based on the Oracle
39 use vars qw[ $DEBUG $WARN $VERSION %used_names ];
41 $DEBUG = 0 unless defined $DEBUG;
43 use base qw(SQL::Translator::Producer);
44 use SQL::Translator::Schema::Constants;
45 use SQL::Translator::Utils qw(debug header_comment parse_dbms_version);
48 my ( %translate, %index_name );
62 mediumint => 'integer',
63 smallint => 'smallint',
64 tinyint => 'smallint',
66 varchar => 'character varying',
73 mediumblob => 'bytea',
75 enum => 'character varying',
76 set => 'character varying',
78 datetime => 'timestamp',
80 timestamp => 'timestamp',
88 varchar2 => 'character varying',
98 varchar => 'character varying',
99 datetime => 'timestamp',
104 tinyint => 'smallint',
110 my %reserved = map { $_, 1 } qw[
111 ALL ANALYSE ANALYZE AND ANY AS ASC
113 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
114 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
115 DEFAULT DEFERRABLE DESC DISTINCT DO
117 FALSE FOR FOREIGN FREEZE FROM FULL
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
128 # my $max_id_length = 62;
129 my %used_identifiers = ();
136 =head1 PostgreSQL Create Table Syntax
138 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
139 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
140 | table_constraint } [, ... ]
142 [ INHERITS ( parent_table [, ... ] ) ]
143 [ WITH OIDS | WITHOUT OIDS ]
145 where column_constraint is:
147 [ CONSTRAINT constraint_name ]
148 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
150 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
151 [ ON DELETE action ] [ ON UPDATE action ] }
152 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
154 and table_constraint is:
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 ]
164 =head1 Create Index Syntax
166 CREATE [ UNIQUE ] INDEX index_name ON table
167 [ USING acc_method ] ( column [ ops_name ] [, ...] )
169 CREATE [ UNIQUE ] INDEX index_name ON table
170 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
175 # -------------------------------------------------------------------
177 my $translator = shift;
178 local $DEBUG = $translator->debug;
179 local $WARN = $translator->show_warnings;
180 my $no_comments = $translator->no_comments;
181 my $add_drop_table = $translator->add_drop_table;
182 my $schema = $translator->schema;
183 my $pargs = $translator->producer_args;
184 my $postgres_version = parse_dbms_version(
185 $pargs->{postgres_version}, 'perl'
188 my $qt = $translator->quote_table_names ? q{"} : q{};
189 my $qf = $translator->quote_field_names ? q{"} : q{};
192 push @output, header_comment unless ($no_comments);
194 my (@table_defs, @fks);
195 for my $table ( $schema->get_tables ) {
197 my ($table_def, $fks) = create_table($table, {
198 quote_table_names => $qt,
199 quote_field_names => $qf,
200 no_comments => $no_comments,
201 postgres_version => $postgres_version,
202 add_drop_table => $add_drop_table,
205 push @table_defs, $table_def;
209 for my $view ( $schema->get_views ) {
210 push @table_defs, create_view($view, {
211 add_drop_view => $add_drop_table,
212 quote_table_names => $qt,
213 quote_field_names => $qf,
214 no_comments => $no_comments,
218 push @output, map { "$_;\n\n" } @table_defs;
220 push @output, "--\n-- Foreign Key Definitions\n--\n\n" unless $no_comments;
221 push @output, map { "$_;\n\n" } @fks;
226 warn "Truncated " . keys( %truncated ) . " names:\n";
227 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
231 warn "Encounted " . keys( %unreserve ) .
232 " unsafe names in schema (reserved or invalid):\n";
233 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
239 : join ('', @output);
242 # -------------------------------------------------------------------
244 my $basename = shift || '';
245 my $type = shift || '';
246 my $scope = shift || '';
247 my $critical = shift || '';
248 my $basename_orig = $basename;
249 # my $max_id_length = 62;
251 ? $max_id_length - (length($type) + 1)
253 $basename = substr( $basename, 0, $max_name )
254 if length( $basename ) > $max_name;
255 my $name = $type ? "${type}_$basename" : $basename;
257 if ( $basename ne $basename_orig and $critical ) {
258 my $show_type = $type ? "+'$type'" : "";
259 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
260 "character limit to make '$name'\n" if $WARN;
261 $truncated{ $basename_orig } = $name;
264 $scope ||= \%global_names;
265 if ( my $prev = $scope->{ $name } ) {
266 my $name_orig = $name;
267 $name .= sprintf( "%02d", ++$prev );
268 substr($name, $max_id_length - 3) = "00"
269 if length( $name ) > $max_id_length;
271 warn "The name '$name_orig' has been changed to ",
272 "'$name' to make it unique.\n" if $WARN;
274 $scope->{ $name_orig }++;
281 # -------------------------------------------------------------------
283 my $name = shift || '';
284 my $schema_obj_name = shift || '';
286 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
288 # also trap fields that don't begin with a letter
289 return $name if (!$reserved{ uc $name }) && $name =~ /^[a-z]/i;
291 if ( $schema_obj_name ) {
292 ++$unreserve{"$schema_obj_name.$name"};
295 ++$unreserve{"$name (table name)"};
298 my $unreserve = sprintf '%s_', $name;
299 return $unreserve.$suffix;
302 # -------------------------------------------------------------------
303 sub next_unused_name {
304 my $orig_name = shift or return;
305 my $name = $orig_name;
307 my $suffix_gen = sub {
309 return ++$suffix ? '' : $suffix;
313 $name = $orig_name . $suffix_gen->();
314 last if $used_names{ $name }++;
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;
330 my $table_name = $table->name or next;
331 my ( $fql_tbl_name ) = ( $table_name =~ s/\W(.*)$// ) ? $1 : q{};
332 my $table_name_ur = $qt ? $table_name
333 : $fql_tbl_name ? join('.', $table_name, unreserve($fql_tbl_name))
334 : unreserve($table_name);
335 $table->name($table_name_ur);
337 # print STDERR "$table_name table_name\n";
338 my ( @comments, @field_defs, @sequence_defs, @constraint_defs, @type_defs, @type_drops, @fks );
340 push @comments, "--\n-- Table: $table_name_ur\n--\n" unless $no_comments;
342 if ( $table->comments and !$no_comments ){
343 my $c = "-- Comments: \n-- ";
344 $c .= join "\n-- ", $table->comments;
352 my %field_name_scope;
353 for my $field ( $table->get_fields ) {
354 push @field_defs, create_field($field, { quote_table_names => $qt,
355 quote_field_names => $qf,
356 table_name => $table_name_ur,
357 postgres_version => $postgres_version,
358 type_defs => \@type_defs,
359 type_drops => \@type_drops,
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];
406 $create_statement .= join (";\n", @type_drops) . ";\n"
407 if $postgres_version >= 8.003 && scalar @type_drops;
409 $create_statement .= qq[DROP TABLE $qt$table_name_ur$qt CASCADE;\n];
412 $create_statement .= join(";\n", @type_defs) . ";\n"
413 if $postgres_version >= 8.003 && scalar @type_defs;
414 $create_statement .= qq[CREATE ${temporary}TABLE $qt$table_name_ur$qt (\n].
415 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
418 $create_statement .= @index_defs ? ';' : q{};
419 $create_statement .= ( $create_statement =~ /;$/ ? "\n" : q{} )
420 . join(";\n", @index_defs);
422 return $create_statement, \@fks;
426 my ($view, $options) = @_;
427 my $qt = $options->{quote_table_names} || '';
428 my $qf = $options->{quote_field_names} || '';
429 my $add_drop_view = $options->{add_drop_view};
431 my $view_name = $view->name;
432 debug("PKG: Looking at view '${view_name}'\n");
435 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n"
436 unless $options->{no_comments};
437 $create .= "DROP VIEW ${qt}${view_name}${qt};\n" if $add_drop_view;
440 my $extra = $view->extra;
441 $create .= " TEMPORARY" if exists($extra->{temporary}) && $extra->{temporary};
442 $create .= " VIEW ${qt}${view_name}${qt}";
444 if ( my @fields = $view->fields ) {
445 my $field_list = join ', ', map { "${qf}${_}${qf}" } @fields;
446 $create .= " ( ${field_list} )";
449 if ( my $sql = $view->sql ) {
450 $create .= " AS\n ${sql}\n";
453 if ( $extra->{check_option} ) {
454 $create .= ' WITH ' . uc $extra->{check_option} . ' CHECK OPTION';
462 my %field_name_scope;
466 my ($field, $options) = @_;
468 my $qt = $options->{quote_table_names} || '';
469 my $qf = $options->{quote_field_names} || '';
470 my $table_name = $field->table->name;
471 my $constraint_defs = $options->{constraint_defs} || [];
472 my $postgres_version = $options->{postgres_version} || 0;
473 my $type_defs = $options->{type_defs} || [];
474 my $type_drops = $options->{type_drops} || [];
476 $field_name_scope{$table_name} ||= {};
477 my $field_name = $field->name;
478 my $field_name_ur = $qf ? $field_name : unreserve($field_name, $table_name );
479 $field->name($field_name_ur);
480 my $field_comments = $field->comments
481 ? "-- " . $field->comments . "\n "
484 my $field_def = $field_comments.qq[$qf$field_name_ur$qf];
489 my @size = $field->size;
490 my $data_type = lc $field->data_type;
491 my %extra = $field->extra;
492 my $list = $extra{'list'} || [];
493 # todo deal with embedded quotes
494 my $commalist = join( ', ', map { qq['$_'] } @$list );
496 if ($postgres_version >= 8.003 && $field->data_type eq 'enum') {
497 my $type_name = $field->table->name . '_' . $field->name . '_type';
498 $field_def .= ' '. $type_name;
499 push @$type_defs, "CREATE TYPE $type_name AS ENUM ($commalist)";
500 push @$type_drops, "DROP TYPE IF EXISTS $type_name";
502 $field_def .= ' '. convert_datatype($field);
508 my $default = $field->default_value;
509 if ( defined $default ) {
510 SQL::Translator::Producer->_apply_default_value(
516 'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
522 # Not null constraint
524 $field_def .= ' NOT NULL' unless $field->is_nullable;
532 my ($index, $options) = @_;
534 my $qt = $options->{quote_table_names} ||'';
535 my $qf = $options->{quote_field_names} ||'';
536 my $table_name = $index->table->name;
537 # my $table_name_ur = $qt ? unreserve($table_name) : $table_name;
539 my ($index_def, @constraint_defs);
541 my $name = next_unused_name(
543 || join('_', $table_name, 'idx', ++$index_name{ $table_name })
546 my $type = $index->type || NORMAL;
548 map { $_ =~ s/\(.+\)//; $_ }
549 map { $qt ? $_ : unreserve($_, $table_name ) }
553 my $def_start = qq[CONSTRAINT "$name" ];
554 if ( $type eq PRIMARY_KEY ) {
555 push @constraint_defs, "${def_start}PRIMARY KEY ".
556 '(' .$qf . join( $qf. ', '.$qf, @fields ) . $qf . ')';
558 elsif ( $type eq UNIQUE ) {
559 push @constraint_defs, "${def_start}UNIQUE " .
560 '(' . $qf . join( $qf. ', '.$qf, @fields ) . $qf.')';
562 elsif ( $type eq NORMAL ) {
564 "CREATE INDEX ${qf}${name}${qf} on ${qt}${table_name}${qt} (".
565 join( ', ', map { qq[$qf$_$qf] } @fields ).
570 warn "Unknown index type ($type) on table $table_name.\n"
574 return $index_def, \@constraint_defs;
577 sub create_constraint
579 my ($c, $options) = @_;
581 my $qf = $options->{quote_field_names} ||'';
582 my $qt = $options->{quote_table_names} ||'';
583 my $table_name = $c->table->name;
584 my (@constraint_defs, @fks);
586 my $name = $c->name || '';
588 $name = next_unused_name($name);
592 map { $_ =~ s/\(.+\)//; $_ }
593 map { $qt ? $_ : unreserve( $_, $table_name )}
597 map { $_ =~ s/\(.+\)//; $_ }
598 map { $qt ? $_ : unreserve( $_, $table_name )}
599 $c->reference_fields;
601 next if !@fields && $c->type ne CHECK_C;
602 my $def_start = $name ? qq[CONSTRAINT "$name" ] : '';
603 if ( $c->type eq PRIMARY_KEY ) {
604 push @constraint_defs, "${def_start}PRIMARY KEY ".
605 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
607 elsif ( $c->type eq UNIQUE ) {
608 $name = next_unused_name($name);
609 push @constraint_defs, "${def_start}UNIQUE " .
610 '('.$qf . join( $qf.', '.$qf, @fields ) . $qf.')';
612 elsif ( $c->type eq CHECK_C ) {
613 my $expression = $c->expression;
614 push @constraint_defs, "${def_start}CHECK ($expression)";
616 elsif ( $c->type eq FOREIGN_KEY ) {
617 my $def .= "ALTER TABLE ${qt}${table_name}${qt} ADD FOREIGN KEY (" .
618 join( ', ', map { qq[$qf$_$qf] } @fields ) . ')' .
619 "\n REFERENCES " . $qt . $c->reference_table . $qt;
622 $def .= ' ('.$qf . join( $qf.', '.$qf, @rfields ) . $qf.')';
625 if ( $c->match_type ) {
627 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
630 if ( $c->on_delete ) {
631 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
634 if ( $c->on_update ) {
635 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
638 if ( $c->deferrable ) {
639 $def .= ' DEFERRABLE';
645 return \@constraint_defs, \@fks;
652 my @size = $field->size;
653 my $data_type = lc $field->data_type;
655 if ( $data_type eq 'enum' ) {
657 # $len = ($len < length($_)) ? length($_) : $len for (@$list);
658 # my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
659 # push @$constraint_defs,
660 # qq[CONSTRAINT "$chk_name" CHECK ($qf$field_name$qf ].
661 # qq[IN ($commalist))];
662 $data_type = 'character varying';
664 elsif ( $data_type eq 'set' ) {
665 $data_type = 'character varying';
667 elsif ( $field->is_auto_increment ) {
668 if ( defined $size[0] && $size[0] > 11 ) {
669 $data_type = 'bigserial';
672 $data_type = 'serial';
677 $data_type = defined $translate{ $data_type } ?
678 $translate{ $data_type } :
682 if ( $data_type =~ /^time/i || $data_type =~ /^interval/i ) {
683 if ( defined $size[0] && $size[0] > 6 ) {
688 if ( $data_type eq 'integer' ) {
689 if ( defined $size[0] && $size[0] > 0) {
690 if ( $size[0] > 10 ) {
691 $data_type = 'bigint';
693 elsif ( $size[0] < 5 ) {
694 $data_type = 'smallint';
697 $data_type = 'integer';
701 $data_type = 'integer';
705 my $type_with_size = join('|',
706 'bit', 'varbit', 'character', 'bit varying', 'character varying',
707 'time', 'timestamp', 'interval', 'numeric'
710 if ( $data_type !~ /$type_with_size/ ) {
714 if (defined $size[0] && $size[0] > 0 && $data_type =~ /^time/i ) {
715 $data_type =~ s/^(time.*?)( with.*)?$/$1($size[0])/;
716 $data_type .= $2 if(defined $2);
717 } elsif ( defined $size[0] && $size[0] > 0 ) {
718 $data_type .= '(' . join( ',', @size ) . ')';
727 my ($from_field, $to_field) = @_;
729 die "Can't alter field in another table"
730 if($from_field->table->name ne $to_field->table->name);
733 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET NOT NULL',
734 $to_field->table->name,
735 $to_field->name) if(!$to_field->is_nullable and
736 $from_field->is_nullable);
738 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL',
739 $to_field->table->name,
741 if ( !$from_field->is_nullable and $to_field->is_nullable );
744 my $from_dt = convert_datatype($from_field);
745 my $to_dt = convert_datatype($to_field);
746 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE %s',
747 $to_field->table->name,
749 $to_dt) if($to_dt ne $from_dt);
751 push @out, sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s',
752 $to_field->table->name,
754 $to_field->name) if($from_field->name ne $to_field->name);
756 my $old_default = $from_field->default_value;
757 my $new_default = $to_field->default_value;
758 my $default_value = $to_field->default_value;
760 # fixes bug where output like this was created:
761 # ALTER TABLE users ALTER COLUMN column SET DEFAULT ThisIsUnescaped;
762 if(ref $default_value eq "SCALAR" ) {
763 $default_value = $$default_value;
764 } elsif( defined $default_value && $to_dt =~ /^(character|text)/xsmi ) {
765 $default_value =~ s/'/''/xsmg;
766 $default_value = q(') . $default_value . q(');
769 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
770 $to_field->table->name,
773 if ( defined $new_default &&
774 (!defined $old_default || $old_default ne $new_default) );
776 # fixes bug where removing the DEFAULT statement of a column
777 # would result in no change
779 push @out, sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
780 $to_field->table->name,
782 if ( !defined $new_default && defined $old_default );
785 return wantarray ? @out : join("\n", @out);
788 sub rename_field { alter_field(@_) }
792 my ($new_field) = @_;
794 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
795 $new_field->table->name,
796 create_field($new_field));
803 my ($old_field) = @_;
805 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
806 $old_field->table->name,
813 my ($to_table, $options) = @_;
814 my $qt = $options->{quote_table_names} || '';
815 my $out = sprintf('ALTER TABLE %s %s',
816 $qt . $to_table->name . $qt,
817 $options->{alter_table_action});
822 my ($old_table, $new_table, $options) = @_;
823 my $qt = $options->{quote_table_names} || '';
824 $options->{alter_table_action} = "RENAME TO $qt$new_table$qt";
825 return alter_table($old_table, $options);
828 sub alter_create_index {
829 my ($index, $options) = @_;
830 my $qt = $options->{quote_table_names} || '';
831 my $qf = $options->{quote_field_names} || '';
832 my ($idef, $constraints) = create_index($index, {
833 quote_field_names => $qf,
834 quote_table_names => $qt,
835 table_name => $index->table->name,
837 return $index->type eq NORMAL ? $idef
838 : sprintf('ALTER TABLE %s ADD %s',
839 $qt . $index->table->name . $qt,
840 join(q{}, @$constraints)
844 sub alter_drop_index {
845 my ($index, $options) = @_;
846 my $index_name = $index->name;
847 return "DROP INDEX $index_name";
850 sub alter_drop_constraint {
851 my ($c, $options) = @_;
852 my $qt = $options->{quote_table_names} || '';
853 my $qc = $options->{quote_field_names} || '';
854 my $out = sprintf('ALTER TABLE %s DROP CONSTRAINT %s',
855 $qt . $c->table->name . $qt,
856 $qc . $c->name . $qc );
860 sub alter_create_constraint {
861 my ($index, $options) = @_;
862 my $qt = $options->{quote_table_names} || '';
863 my ($defs, $fks) = create_constraint(@_);
865 # return if there are no constraint definitions so we don't run
866 # into output like this:
867 # ALTER TABLE users ADD ;
869 return unless(@{$defs} || @{$fks});
870 return $index->type eq FOREIGN_KEY ? join(q{}, @{$fks})
871 : join( ' ', 'ALTER TABLE', $qt.$index->table->name.$qt,
872 'ADD', join(q{}, @{$defs}, @{$fks})
877 my ($table, $options) = @_;
878 my $qt = $options->{quote_table_names} || '';
879 return "DROP TABLE $qt$table$qt CASCADE";
884 # -------------------------------------------------------------------
885 # Life is full of misery, loneliness, and suffering --
886 # and it's all over much too soon.
888 # -------------------------------------------------------------------
894 SQL::Translator, SQL::Translator::Producer::Oracle.
898 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.