text back to clob
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Oracle.pm
1 package SQL::Translator::Producer::Oracle;
2
3 # -------------------------------------------------------------------
4 # Copyright (C) 2002-2009 SQLFairy Authors
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
21 =head1 NAME
22
23 SQL::Translator::Producer::Oracle - Oracle SQL producer
24
25 =head1 SYNOPSIS
26
27   use SQL::Translator;
28
29   my $t = SQL::Translator->new( parser => '...', producer => 'Oracle' );
30   print $translator->translate( $file );
31
32 =head1 DESCRIPTION
33
34 Creates an SQL DDL suitable for Oracle.
35
36 =head1 producer_args
37
38 =over
39
40 =item delay_constraints
41
42 This option remove the primary key and other key constraints from the
43 CREATE TABLE statement and adds ALTER TABLEs at the end with it.
44
45 =item quote_field_names
46
47 Controls whether quotes are being used around column names in generated DDL.
48
49 =item quote_table_names
50
51 Controls whether quotes are being used around table, sequence and trigger names in
52 generated DDL.
53
54 =back
55
56 =head1 NOTES
57
58 =head2 Autoincremental primary keys
59
60 This producer uses sequences and triggers to autoincrement primary key
61 columns, if necessary. SQLPlus and DBI expect a slightly different syntax
62 of CREATE TRIGGER statement. You might have noticed that this
63 producer returns a scalar containing all statements concatenated by
64 newlines or an array of single statements depending on the context
65 (scalar, array) it has been called in.
66
67 SQLPlus expects following trigger syntax:
68
69     CREATE OR REPLACE TRIGGER ai_person_id
70     BEFORE INSERT ON person
71     FOR EACH ROW WHEN (
72      new.id IS NULL OR new.id = 0
73     )
74     BEGIN
75      SELECT sq_person_id.nextval
76      INTO :new.id
77      FROM dual;
78     END;
79     /
80
81 Whereas if you want to create the same trigger using L<DBI/do>, you need
82 to omit the last slash:
83
84     my $dbh = DBI->connect('dbi:Oracle:mysid', 'scott', 'tiger');
85     $dbh->do("
86         CREATE OR REPLACE TRIGGER ai_person_id
87         BEFORE INSERT ON person
88         FOR EACH ROW WHEN (
89          new.id IS NULL OR new.id = 0
90         )
91         BEGIN
92          SELECT sq_person_id.nextval
93          INTO :new.id
94          FROM dual;
95         END;
96     ");
97
98 If you call this producer in array context, we expect you want to process
99 the returned array of statements using L<DBI> like
100 L<DBIx::Class::Schema/deploy> does.
101
102 To get this working we removed the slash in those statements in version
103 0.09002 of L<SQL::Translator> when called in array context. In scalar
104 context the slash will be still there to ensure compatibility with SQLPlus.
105
106 =cut
107
108 use strict;
109 use vars qw[ $VERSION $DEBUG $WARN ];
110 $VERSION = '1.59';
111 $DEBUG   = 0 unless defined $DEBUG;
112
113 use SQL::Translator::Schema::Constants;
114 use SQL::Translator::Utils qw(header_comment);
115
116 my %translate  = (
117     #
118     # MySQL types
119     #
120     bigint     => 'number',
121     double     => [ 'float', 126 ],
122     decimal    => 'number',
123     float      => 'float',
124     int        => 'number',
125     integer    => 'number',
126     mediumint  => 'number',
127     smallint   => 'number',
128     tinyint    => 'number',
129     char       => 'char',
130     varchar    => 'varchar2',
131     tinyblob   => 'blob',
132     blob       => 'blob',
133     mediumblob => 'blob',
134     longblob   => 'blob',
135     tinytext   => 'varchar2',
136     text       => 'clob',
137     longtext   => 'clob',
138     mediumtext => 'clob',
139     enum       => 'varchar2',
140     set        => 'varchar2',
141     date       => 'date',
142     datetime   => 'date',
143     time       => 'date',
144     timestamp  => 'date',
145     year       => 'date',
146
147     #
148     # PostgreSQL types
149     #
150     numeric             => 'number',
151     'double precision'  => 'number',
152     serial              => 'number',
153     bigserial           => 'number',
154     money               => 'number',
155     character           => 'char',
156     'character varying' => 'varchar2',
157     bytea               => 'BLOB',
158     interval            => 'number',
159     boolean             => 'number',
160     point               => 'number',
161     line                => 'number',
162     lseg                => 'number',
163     box                 => 'number',
164     path                => 'number',
165     polygon             => 'number',
166     circle              => 'number',
167     cidr                => 'number',
168     inet                => 'varchar2',
169     macaddr             => 'varchar2',
170     bit                 => 'number',
171     'bit varying'       => 'number',
172
173     #
174     # Oracle types
175     #
176     number              => 'number',
177     varchar2            => 'varchar2',
178     long                => 'clob',
179 );
180
181 #
182 # Oracle 8/9 max size of data types from:
183 # http://www.ss64.com/orasyntax/datatypes.html
184 #
185 my %max_size = (
186     char      => 2000,
187     float     => 126,
188     nchar     => 2000,
189     nvarchar2 => 4000,
190     number    => [ 38, 127 ],
191     raw       => 2000,
192     varchar   => 4000,          # only synonym for varchar2
193     varchar2  => 4000,
194 );
195
196 my $max_id_length    = 30;
197 my %used_identifiers = ();
198 my %global_names;
199 my %truncated;
200
201 # Quote used to escape table, field, sequence and trigger names
202 my $quote_char  = '"';
203
204 # -------------------------------------------------------------------
205 sub produce {
206     my $translator     = shift;
207     $DEBUG             = $translator->debug;
208     $WARN              = $translator->show_warnings || 0;
209     my $no_comments    = $translator->no_comments;
210     my $add_drop_table = $translator->add_drop_table;
211     my $schema         = $translator->schema;
212     my $oracle_version  = $translator->producer_args->{oracle_version} || 0;
213     my $delay_constraints = $translator->producer_args->{delay_constraints};
214     my ($output, $create, @table_defs, @fk_defs, @trigger_defs, @index_defs, @constraint_defs);
215
216     $create .= header_comment unless ($no_comments);
217     my $qt = 1 if $translator->quote_table_names;
218     my $qf = 1 if $translator->quote_field_names;
219
220     if ( $translator->parser_type =~ /mysql/i ) {
221         $create .= 
222             "-- We assume that default NLS_DATE_FORMAT has been changed\n".
223             "-- but we set it here anyway to be self-consistent.\n"
224             unless $no_comments;
225
226         $create .= 
227         "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';\n\n";
228     }
229
230     for my $table ( $schema->get_tables ) { 
231         my ( $table_def, $fk_def, $trigger_def, $index_def, $constraint_def ) = create_table(
232             $table,
233             {
234                 add_drop_table    => $add_drop_table,
235                 show_warnings     => $WARN,
236                 no_comments       => $no_comments,
237                 delay_constraints => $delay_constraints,
238                 quote_table_names => $qt,
239                 quote_field_names => $qf,
240             }
241         );
242         push @table_defs, @$table_def;
243         push @fk_defs, @$fk_def;
244         push @trigger_defs, @$trigger_def;
245         push @index_defs, @$index_def;
246         push @constraint_defs, @$constraint_def;
247     }
248
249     my (@view_defs);
250     foreach my $view ( $schema->get_views ) {
251         my ( $view_def ) = create_view(
252             $view,
253             {
254                 add_drop_view     => $add_drop_table,
255                 quote_table_names => $qt,
256             }
257         );
258         push @view_defs, @$view_def;
259     }
260
261     if (wantarray) {
262         return defined $create ? $create : (), @table_defs, @view_defs, @fk_defs, @trigger_defs, @index_defs, @constraint_defs;
263     }
264     else {
265         $create .= join (";\n\n", @table_defs, @view_defs, @fk_defs, @index_defs, @constraint_defs);
266         $create .= ";\n\n";
267         # If wantarray is not set we have to add "/" in this statement
268         # DBI->do() needs them omitted
269         # triggers may NOT end with a semicolon
270         $create .= join "/\n\n", @trigger_defs;
271         # for last trigger
272         $create .= "/\n\n";
273         return $create;
274     }
275 }
276
277 sub create_table {
278     my ($table, $options) = @_;
279     my $qt = $options->{quote_table_names};
280     my $qf = $options->{quote_field_names};
281     my $table_name = $table->name;
282     my $table_name_q = quote($table_name,$qt);
283
284     my $item = '';
285     my $drop;
286     my (@create, @field_defs, @constraint_defs, @fk_defs, @trigger_defs);
287
288     push @create, "--\n-- Table: $table_name\n--" unless $options->{no_comments};
289     push @create, qq[DROP TABLE $table_name_q CASCADE CONSTRAINTS] if $options->{add_drop_table};
290
291         my ( %field_name_scope, @field_comments );
292         for my $field ( $table->get_fields ) {
293             my ($field_create, $field_defs, $trigger_defs, $field_comments) =
294               create_field($field, $options, \%field_name_scope);
295             push @create, @$field_create if ref $field_create;
296             push @field_defs, @$field_defs if ref $field_defs;
297             push @trigger_defs, @$trigger_defs if ref $trigger_defs;
298             push @field_comments, @$field_comments if ref $field_comments;
299         }
300
301         #
302         # Table options
303         #
304         my @table_options;
305         for my $opt ( $table->options ) {
306             if ( ref $opt eq 'HASH' ) {
307                 my ( $key, $value ) = each %$opt;
308                 if ( ref $value eq 'ARRAY' ) {
309                     push @table_options, "$key\n(\n".  join ("\n",
310                         map { "  $_->[0]\t$_->[1]" } 
311                         map { [ each %$_ ] }
312                         @$value
313                     )."\n)";
314                 }
315                 elsif ( !defined $value ) {
316                     push @table_options, $key;
317                 }
318                 else {
319                     push @table_options, "$key    $value";
320                 }
321             }
322         }
323
324         #
325         # Table constraints
326         #
327         for my $c ( $table->get_constraints ) {
328             my $name    = $c->name || '';
329             my @fields  = map { quote($_,$qf) } $c->fields;
330             my @rfields = quote($c->reference_fields,$qf);
331             next if !@fields && $c->type ne CHECK_C;
332
333             if ( $c->type eq PRIMARY_KEY ) {
334                 # create a name if delay_constraints
335                 $name ||= mk_name( $table_name, 'pk' )
336                   if $options->{delay_constraints};
337                 $name = quote($name,$qf);
338                 push @constraint_defs, ($name ? "CONSTRAINT $name " : '') .
339                   'PRIMARY KEY (' . join( ', ', @fields ) . ')';
340             }
341             elsif ( $c->type eq UNIQUE ) {
342               # Don't create UNIQUE constraints identical to the primary key
343               if ( my $pk = $table->primary_key ) {
344                 my $u_fields = join(":", @fields);
345                 my $pk_fields = join(":", $pk->fields);
346                 next if $u_fields eq $pk_fields;
347               }
348
349               if ($name) {
350                 # Force prepend of table_name as ORACLE doesn't allow duplicate
351                 # CONSTRAINT names even for different tables (ORA-02264)
352                 $name = "${table_name}_$name" unless $name =~ /^$table_name/;
353               }
354               else {
355                 $name = mk_name( $table_name, 'u' );
356               }
357
358               $name = quote($name, $qf);
359
360                 for my $f ( $c->fields ) {
361                     my $field_def = $table->get_field( $f ) or next;
362                     my $dtype     = $translate{ ref $field_def->data_type eq "ARRAY" ? $field_def->data_type->[0] : $field_def->data_type} or next;
363                     if ( $WARN && $dtype =~ /clob/i ) {
364                         warn "Oracle will not allow UNIQUE constraints on " .
365                              "CLOB field '" . $field_def->table->name . '.' .
366                              $field_def->name . ".'\n"
367                     }
368                 }
369
370                 push @constraint_defs, "CONSTRAINT $name UNIQUE " .
371                     '(' . join( ', ', @fields ) . ')';
372             }
373             elsif ( $c->type eq CHECK_C ) {
374                 $name ||= mk_name( $name || $table_name, 'ck' );
375                 $name = quote($name, $qf);
376                 my $expression = $c->expression || '';
377                 push @constraint_defs, "CONSTRAINT $name CHECK ($expression)";
378             }
379             elsif ( $c->type eq FOREIGN_KEY ) {
380                 $name = mk_name( join('_', $table_name, $c->fields). '_fk' );
381                 $name = quote($name, $qf);
382                 my $def = "CONSTRAINT $name FOREIGN KEY ";
383
384                 if ( @fields ) {
385                     $def .= '(' . join( ', ', @fields ) . ')';
386                 }
387
388                 my $ref_table = quote($c->reference_table,$qt);
389
390                 $def .= " REFERENCES $ref_table";
391
392                 if ( @rfields ) {
393                     $def .= ' (' . join( ', ', @rfields ) . ')';
394                 }
395
396                 if ( $c->match_type ) {
397                     $def .= ' MATCH ' . 
398                         ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
399                 }
400
401                 if ( $c->on_delete ) {
402                     $def .= ' ON DELETE '.join( ' ', $c->on_delete );
403                 }
404
405                 # disabled by plu 2007-12-29 - doesn't exist for oracle
406                 #if ( $c->on_update ) {
407                 #    $def .= ' ON UPDATE '.join( ' ', $c->on_update );
408                 #}
409
410                 push @fk_defs, sprintf("ALTER TABLE %s ADD %s", $table_name_q, $def);
411             }
412         }
413
414         #
415         # Index Declarations
416         #
417         my @index_defs = ();
418         for my $index ( $table->get_indices ) {
419             my $index_name = $index->name || '';
420             my $index_type = $index->type || NORMAL;
421             my @fields     = map { quote($_, $qf) } $index->fields;
422             next unless @fields;
423
424             my @index_options;
425             for my $opt ( $index->options ) {
426                 if ( ref $opt eq 'HASH' ) {
427                     my ( $key, $value ) = each %$opt;
428                     if ( ref $value eq 'ARRAY' ) {
429                         push @table_options, "$key\n(\n".  join ("\n",
430                             map { "  $_->[0]\t$_->[1]" } 
431                             map { [ each %$_ ] }
432                            @$value
433                         )."\n)";
434                     }
435                     elsif ( !defined $value ) {
436                         push @index_options, $key;
437                     }
438                     else {
439                         push @index_options, "$key    $value";
440                     }
441                 }
442             }
443             my $index_options = @index_options
444               ? "\n".join("\n", @index_options) : '';
445
446             if ( $index_type eq PRIMARY_KEY ) {
447                 $index_name = $index_name ? mk_name( $index_name ) 
448                     : mk_name( $table_name, 'pk' );
449                 $index_name = quote($index_name, $qf);
450                 push @field_defs, 'CONSTRAINT '.$index_name.' PRIMARY KEY '.
451                     '(' . join( ', ', @fields ) . ')';
452             }
453             elsif ( $index_type eq NORMAL ) {
454                 $index_name = $index_name ? mk_name( $index_name ) 
455                     : mk_name( $table_name, $index_name || 'i' );
456                 $index_name = quote($index_name, $qf);
457                 push @index_defs, 
458                     "CREATE INDEX $index_name on $table_name_q (".
459                         join( ', ', @fields ).  
460                     ")$index_options";
461             }
462             elsif ( $index_type eq UNIQUE ) {
463                 $index_name = $index_name ? mk_name( $index_name ) 
464                     : mk_name( $table_name, $index_name || 'i' );
465                 $index_name = quote($index_name, $qf);
466                 push @index_defs, 
467                     "CREATE UNIQUE INDEX $index_name on $table_name_q (".
468                         join( ', ', @fields ).  
469                     ")$index_options"; 
470             }
471             else {
472                 warn "Unknown index type ($index_type) on table $table_name.\n"
473                     if $WARN;
474             }
475         }
476
477         if ( my @table_comments = $table->comments ) {
478             for my $comment ( @table_comments ) {
479                 next unless $comment;
480                 $comment =~ s/'/''/g;
481                 push @field_comments, "COMMENT ON TABLE $table_name_q is\n '".
482                 $comment . "'" unless $options->{no_comments}
483                 ;
484             }
485         }
486
487         my $table_options = @table_options 
488             ? "\n".join("\n", @table_options) : '';
489     push @create, "CREATE TABLE $table_name_q (\n" .
490             join( ",\n", map { "  $_" } @field_defs,
491             ($options->{delay_constraints} ? () : @constraint_defs) ) .
492             "\n)$table_options";
493
494     @constraint_defs = map { "ALTER TABLE $table_name_q ADD $_"  }
495       @constraint_defs;
496
497     if ( $WARN ) {
498         if ( %truncated ) {
499             warn "Truncated " . keys( %truncated ) . " names:\n";
500             warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
501         }
502     }
503
504     return \@create, \@fk_defs, \@trigger_defs, \@index_defs, ($options->{delay_constraints} ? \@constraint_defs : []);
505 }
506
507 sub alter_field {
508     my ($from_field, $to_field, $options) = @_;
509
510     my $qt = $options->{quote_table_names};
511     my ($field_create, $field_defs, $trigger_defs, $field_comments) =
512       create_field($to_field, $options, {});
513
514     # Fix ORA-01442
515     if ($to_field->is_nullable && !$from_field->is_nullable) {
516         die 'Cannot remove NOT NULL from table field';
517     } elsif (!$from_field->is_nullable && !$to_field->is_nullable) {
518         @$field_defs = map { s/ NOT NULL//; $_} @$field_defs;
519     }
520
521     my $table_name = quote($to_field->table->name,$qt);
522
523     return 'ALTER TABLE '.$table_name.' MODIFY ( '.join('', @$field_defs).' )';
524 }
525
526 sub add_field {
527     my ($new_field, $options) = @_;
528
529     my $qt = $options->{quote_table_names};
530     my ($field_create, $field_defs, $trigger_defs, $field_comments) =
531       create_field($new_field, $options, {});
532
533     my $table_name = quote($new_field->table->name,$qt);
534
535     my $out = sprintf('ALTER TABLE %s ADD ( %s )',
536                       $table_name,
537                       join('', @$field_defs));
538     return $out;
539 }
540
541 sub create_field {
542     my ($field, $options, $field_name_scope) = @_;
543     my $qf = $options->{quote_field_names};
544     my $qt = $options->{quote_table_names};
545
546     my (@create, @field_defs, @trigger_defs, @field_comments);
547
548     my $table_name = $field->table->name;
549     my $table_name_q = quote($table_name, $qt);
550
551     #
552     # Field name
553     #
554     my $field_name    = mk_name(
555                                 $field->name, '', $field_name_scope, 1
556                                );
557     my $field_name_q = quote($field_name, $qf);
558     my $field_def     = quote($field_name, $qf);
559     $field->name( $field_name );
560
561     #
562     # Datatype
563     #
564     my $check;
565     my $data_type = lc $field->data_type;
566     my @size      = $field->size;
567     my %extra     = $field->extra;
568     my $list      = $extra{'list'} || [];
569     # \todo deal with embedded quotes
570     my $commalist = join( ', ', map { qq['$_'] } @$list );
571
572     if ( $data_type eq 'enum' ) {
573         $check = "CHECK ($field_name_q IN ($commalist))";
574         $data_type = 'varchar2';
575     }
576     elsif ( $data_type eq 'set' ) {
577         # XXX add a CHECK constraint maybe 
578         # (trickier and slower, than enum :)
579         $data_type = 'varchar2';
580     }
581     else {
582       if (defined $translate{ $data_type }) {
583         if (ref $translate{ $data_type } eq "ARRAY") {
584           ($data_type,$size[0])  = @{$translate{ $data_type }};
585         } else {
586           $data_type  = $translate{ $data_type };
587         }
588       }
589       $data_type ||= 'varchar2';
590     }
591
592     # ensure size is not bigger than max size oracle allows for data type
593     if ( defined $max_size{$data_type} ) {
594         for ( my $i = 0 ; $i < scalar @size ; $i++ ) {
595             my $max =
596               ref( $max_size{$data_type} ) eq 'ARRAY'
597               ? $max_size{$data_type}->[$i]
598               : $max_size{$data_type};
599             $size[$i] = $max if $size[$i] > $max;
600         }
601     }
602
603     #
604     # Fixes ORA-02329: column of datatype LOB cannot be 
605     # unique or a primary key
606     #
607     if ( $data_type eq 'clob' && $field->is_primary_key ) {
608         $data_type = 'varchar2';
609         $size[0]   = 4000;
610         warn "CLOB cannot be a primary key, changing to VARCHAR2\n"
611           if $WARN;
612     }
613
614     if ( $data_type eq 'clob' && $field->is_unique ) {
615         $data_type = 'varchar2';
616         $size[0]   = 4000;
617         warn "CLOB cannot be a unique key, changing to VARCHAR2\n"
618           if $WARN;
619     }
620
621     #
622     # Fixes ORA-00907: missing right parenthesis
623     #
624     if ( $data_type =~ /(date|clob)/i ) {
625         undef @size;
626     }
627
628     $field_def .= " $data_type";
629     if ( defined $size[0] && $size[0] > 0 ) {
630         $field_def .= '(' . join( ',', @size ) . ')';
631     }
632
633     #
634     # Default value
635     #
636     my $default = $field->default_value;
637     if ( defined $default ) {
638         #
639         # Wherein we try to catch a string being used as 
640         # a default value for a numerical field.  If "true/false,"
641         # then sub "1/0," otherwise just test the truthity of the
642         # argument and use that (naive?).
643         #
644         if (ref $default and defined $$default) {
645           $default = $$default;
646         } elsif (ref $default) {
647           $default = 'NULL';
648         } elsif ( 
649             $data_type =~ /^number$/i && 
650             $default   !~ /^-?\d+$/     &&
651             $default   !~ m/null/i
652            ) {
653             if ( $default =~ /^true$/i ) {
654                 $default = "'1'";
655             } elsif ( $default =~ /^false$/i ) {
656                 $default = "'0'";
657             } else {
658                 $default = $default ? "'1'" : "'0'";
659             }
660         } elsif ( 
661                  $data_type =~ /date/ && (
662                                           $default eq 'current_timestamp' 
663                                           ||
664                                           $default eq 'now()' 
665                                          )
666                 ) {
667             $default = 'SYSDATE';
668         } else {
669             $default = $default =~ m/null/i ? 'NULL' : "'$default'"
670         } 
671
672         $field_def .= " DEFAULT $default",
673     }
674
675     #
676     # Not null constraint
677     #
678     unless ( $field->is_nullable ) {
679         $field_def .= ' NOT NULL';
680     }
681
682     $field_def .= " $check" if $check;
683
684     #
685     # Auto_increment
686     #
687     if ( $field->is_auto_increment ) {
688         my $base_name    = $table_name . "_". $field_name;
689         my $seq_name     = quote(mk_name( $base_name, 'sq' ),$qt);
690         my $trigger_name = quote(mk_name( $base_name, 'ai' ),$qt);
691
692         push @create, qq[DROP SEQUENCE $seq_name] if $options->{add_drop_table};
693         push @create, "CREATE SEQUENCE $seq_name";
694         my $trigger =
695           "CREATE OR REPLACE TRIGGER $trigger_name\n" .
696           "BEFORE INSERT ON $table_name_q\n" .
697           "FOR EACH ROW WHEN (\n" .
698           " new.$field_name_q IS NULL".
699           " OR new.$field_name_q = 0\n".
700           ")\n".
701           "BEGIN\n" .
702           " SELECT $seq_name.nextval\n" .
703           " INTO :new." . $field_name_q."\n" .
704           " FROM dual;\n" .
705           "END;\n";
706
707         push @trigger_defs, $trigger;
708     }
709
710     if ( lc $field->data_type eq 'timestamp' ) {
711         my $base_name = $table_name . "_". $field_name;
712         my $trig_name = quote(mk_name( $base_name, 'ts' ), $qt);
713         my $trigger = 
714           "CREATE OR REPLACE TRIGGER $trig_name\n".
715           "BEFORE INSERT OR UPDATE ON $table_name_q\n".
716           "FOR EACH ROW WHEN (new.$field_name_q IS NULL)\n".
717           "BEGIN \n".
718           " SELECT sysdate INTO :new.$field_name_q FROM dual;\n".
719           "END;\n";
720
721           push @trigger_defs, $trigger;
722     }
723
724     push @field_defs, $field_def;
725
726     if ( my $comment = $field->comments ) {
727         $comment =~ s/'/''/g;
728         push @field_comments, 
729           "COMMENT ON COLUMN $table_name_q.$field_name_q is\n '" .
730             $comment . "';" unless $options->{no_comments};
731     }
732
733     return \@create, \@field_defs, \@trigger_defs, \@field_comments;
734
735 }
736
737
738 sub create_view {
739     my ($view, $options) = @_;
740     my $qt = $options->{quote_table_names};
741     my $view_name = quote($view->name,$qt);
742     
743     my @create;
744     push @create, qq[DROP VIEW $view_name]
745         if $options->{add_drop_view};
746
747     push @create, sprintf("CREATE VIEW %s AS\n%s",
748                       $view_name,
749                       $view->sql);
750
751     return \@create;
752 }
753
754 # -------------------------------------------------------------------
755 sub mk_name {
756     my $basename      = shift || ''; 
757     my $type          = shift || ''; 
758        $type          = '' if $type =~ /^\d/;
759     my $scope         = shift || ''; 
760     my $critical      = shift || '';
761     my $basename_orig = $basename;
762     my $max_name      = $type 
763                         ? $max_id_length - (length($type) + 1) 
764                         : $max_id_length;
765     $basename         = substr( $basename, 0, $max_name ) 
766                         if length( $basename ) > $max_name;
767     my $name          = $type ? "${type}_$basename" : $basename;
768
769     if ( $basename ne $basename_orig and $critical ) {
770         my $show_type = $type ? "+'$type'" : "";
771         warn "Truncating '$basename_orig'$show_type to $max_id_length ",
772             "character limit to make '$name'\n" if $WARN;
773         $truncated{ $basename_orig } = $name;
774     }
775
776     $scope ||= \%global_names;
777     if ( my $prev = $scope->{ $name } ) {
778         my $name_orig = $name;
779         substr($name, $max_id_length - 2) = ""
780             if length( $name ) >= $max_id_length - 1;
781         $name        .= sprintf( "%02d", $prev++ );
782
783         warn "The name '$name_orig' has been changed to ",
784              "'$name' to make it unique.\n" if $WARN;
785
786         $scope->{ $name_orig }++;
787     }
788
789     $scope->{ $name }++;
790     return $name;
791 }
792
793 1;
794
795 # -------------------------------------------------------------------
796 sub quote {
797   my ($name, $q) = @_;
798   $q && $name ? "$quote_char$name$quote_char" : $name;
799 }
800
801
802 # -------------------------------------------------------------------
803 # All bad art is the result of good intentions.
804 # Oscar Wilde
805 # -------------------------------------------------------------------
806
807 =pod
808
809 =head1 CREDITS
810
811 Mad props to Tim Bunce for much of the logic stolen from his "mysql2ora"
812 script.
813
814 =head1 AUTHORS
815
816 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
817 Alexander Hartmaier E<lt>abraxxa@cpan.orgE<gt>,
818 Fabien Wernli E<lt>faxmodem@cpan.orgE<gt>.
819
820 =head1 SEE ALSO
821
822 SQL::Translator, DDL::Oracle, mysql2ora.
823
824 =cut