Revert my previous changes (rev 1722 reverted back to rev 1721)
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Oracle.pm
index 9585c0f..5db9d7d 100644 (file)
@@ -103,27 +103,6 @@ To get this working we removed the slash in those statements in version
 0.09002 of L<SQL::Translator> when called in array context. In scalar
 context the slash will be still there to ensure compatibility with SQLPlus.
 
-=head2 Quotes
-
-This producer will generate
-DDL with or without quotes if L<quote_table_names> and/or
-L<quote_field_names> are true.
-
-Quotes will be forced and names capitalised if C<quote_table_names==0> and/or C<quote_field_names==0>
-for the following reserved keywords:
-
-    ACCESS ADD ALL ALTER AND ANY AS ASC AUDIT BETWEEN BY CHAR CHECK
-    CLUSTER COLUMN COMMENT COMPRESS CONNECT CREATE CURRENT DATE DECIMAL
-    DEFAULT DELETE DESC DISTINCT DROP ELSE EXCLUSIVE EXISTS FILE FLOAT
-    FOR FROM GRANT GROUP HAVING IDENTIFIED IMMEDIATE IN INCREMENT
-    INDEX INITIAL INSERT INTEGER INTERSECT INTO IS LEVEL LIKE LOCK
-    LONG MAXEXTENTS MINUS MLSLABEL MODE MODIFY NOAUDIT NOCOMPRESS NOT
-    NOWAIT NULL NUMBER OF OFFLINE ON ONLINE OPTION OR ORDER PCTFREE
-    PRIOR PRIVILEGES PUBLIC RAW RENAME RESOURCE REVOKE ROW ROWID ROWNUM
-    ROWS SELECT SESSION SET SHARE SIZE SMALLINT START SUCCESSFUL SYNONYM
-    SYSDATE TABLE THEN TO TRIGGER UID UNION UNIQUE UPDATE USER VALIDATE
-    VALUES VARCHAR VARCHAR2 VIEW WHENEVER WHERE WITH
-
 =cut
 
 use strict;
@@ -139,9 +118,9 @@ my %translate  = (
     # MySQL types
     #
     bigint     => 'number',
-    double     => 'number',
+    double     => 'float',
     decimal    => 'number',
-    float      => 'number',
+    float      => 'float',
     int        => 'number',
     integer    => 'number',
     mediumint  => 'number',
@@ -200,41 +179,12 @@ my %translate  = (
 );
 
 #
-# Oracle reserved words from:
-# http://technet.oracle.com/docs/products/oracle8i/doc_library/\
-# 817_doc/server.817/a85397/ap_keywd.htm
-#
-my %ora_reserved = map { $_, 1 } qw(
-    ACCESS ADD ALL ALTER AND ANY AS ASC AUDIT 
-    BETWEEN BY
-    CHAR CHECK CLUSTER COLUMN COMMENT COMPRESS CONNECT CREATE CURRENT
-    DATE DECIMAL DEFAULT DELETE DESC DISTINCT DROP
-    ELSE EXCLUSIVE EXISTS 
-    FILE FLOAT FOR FROM
-    GRANT GROUP 
-    HAVING
-    IDENTIFIED IMMEDIATE IN INCREMENT INDEX INITIAL INSERT
-    INTEGER INTERSECT INTO IS
-    LEVEL LIKE LOCK LONG 
-    MAXEXTENTS MINUS MLSLABEL MODE MODIFY 
-    NOAUDIT NOCOMPRESS NOT NOWAIT NULL NUMBER 
-    OF OFFLINE ON ONLINE OPTION OR ORDER
-    PCTFREE PRIOR PRIVILEGES PUBLIC
-    RAW RENAME RESOURCE REVOKE ROW ROWID ROWNUM ROWS
-    SELECT SESSION SET SHARE SIZE SMALLINT START 
-    SUCCESSFUL SYNONYM SYSDATE 
-    TABLE THEN TO TRIGGER 
-    UID UNION UNIQUE UPDATE USER
-    VALIDATE VALUES VARCHAR VARCHAR2 VIEW
-    WHENEVER WHERE WITH
-);
-
-#
 # Oracle 8/9 max size of data types from:
 # http://www.ss64.com/orasyntax/datatypes.html
 #
 my %max_size = (
     char      => 2000,
+    float     => 126,
     nchar     => 2000,
     nvarchar2 => 4000,
     number    => [ 38, 127 ],
@@ -250,7 +200,6 @@ my %truncated;
 
 # Quote used to escape table, field, sequence and trigger names
 my $quote_char  = '"';
-my $name_sep    = '.';
 
 # -------------------------------------------------------------------
 sub produce {
@@ -260,14 +209,13 @@ sub produce {
     my $no_comments    = $translator->no_comments;
     my $add_drop_table = $translator->add_drop_table;
     my $schema         = $translator->schema;
-    $quote_char        = $translator->producer_args->{'quote_char'} ||= '"';
-               $name_sep          = $translator->producer_args->{'name_sep'} ||= '.';
+    my $oracle_version  = $translator->producer_args->{oracle_version} || 0;
     my $delay_constraints = $translator->producer_args->{delay_constraints};
     my ($output, $create, @table_defs, @fk_defs, @trigger_defs, @index_defs, @constraint_defs);
 
     $create .= header_comment unless ($no_comments);
-               my $qt = $quote_char if $translator->quote_table_names;
-               my $qf = $quote_char if $translator->quote_field_names;
+    my $qt = 1 if $translator->quote_table_names;
+    my $qf = 1 if $translator->quote_field_names;
 
     if ( $translator->parser_type =~ /mysql/i ) {
         $create .= 
@@ -287,8 +235,8 @@ sub produce {
                 show_warnings     => $WARN,
                 no_comments       => $no_comments,
                 delay_constraints => $delay_constraints,
-                                                               quote_table_names => $qt,
-                                                               quote_field_names => $qf,
+                quote_table_names => $qt,
+                quote_field_names => $qf,
             }
         );
         push @table_defs, @$table_def;
@@ -304,6 +252,7 @@ sub produce {
             $view,
             {
                 add_drop_view     => $add_drop_table,
+                quote_table_names => $qt,
             }
         );
         push @view_defs, @$view_def;
@@ -314,7 +263,7 @@ sub produce {
     }
     else {
         $create .= join (";\n\n", @table_defs, @view_defs, @fk_defs, @index_defs, @constraint_defs);
-                               $create .= ";\n\n";
+        $create .= ";\n\n";
         # If wantarray is not set we have to add "/" in this statement
         # DBI->do() needs them omitted
         # triggers may NOT end with a semicolon
@@ -327,10 +276,10 @@ sub produce {
 
 sub create_table {
     my ($table, $options) = @_;
-               my $qt = $options->{quote_table_names};
-               my $qf = $options->{quote_field_names};
+    my $qt = $options->{quote_table_names};
+    my $qf = $options->{quote_field_names};
     my $table_name = $table->name;
-               my $table_name_q = quote($table_name,$qt);
+    my $table_name_q = quote($table_name,$qt);
 
     my $item = '';
     my $drop;
@@ -378,35 +327,40 @@ sub create_table {
         for my $c ( $table->get_constraints ) {
             my $name    = $c->name || '';
             my @fields  = map { quote($_,$qf) } $c->fields;
-            my @rfields = quote($c->reference_fields,$qf);
+            my @rfields = map { quote($_,$qf) } $c->reference_fields;
+
             next if !@fields && $c->type ne CHECK_C;
 
             if ( $c->type eq PRIMARY_KEY ) {
                 # create a name if delay_constraints
                 $name ||= mk_name( $table_name, 'pk' )
                   if $options->{delay_constraints};
+                $name = quote($name,$qf);
                 push @constraint_defs, ($name ? "CONSTRAINT $name " : '') .
-                       'PRIMARY KEY (' . join( ', ', @fields ) . ')';
+                  'PRIMARY KEY (' . join( ', ', @fields ) . ')';
             }
             elsif ( $c->type eq UNIQUE ) {
-               # Don't create UNIQUE constraints identical to the primary key
-               if ( my $pk = $table->primary_key ) {
-                                       my $u_fields = join(":", @fields);
-                                       my $pk_fields = join(":", $pk->fields);
-                                       next if $u_fields eq $pk_fields;
-               }
-                                                       if ($name) {
-                                                               # Force prepend of table_name as ORACLE doesn't allow duplicate
-                                                               # CONSTRAINT names even for different tables (ORA-02264)
-                                                               $name = "${table_name}_$name" unless $name =~ /^$table_name/;
-                                                       } else {
+              # Don't create UNIQUE constraints identical to the primary key
+              if ( my $pk = $table->primary_key ) {
+                my $u_fields = join(":", @fields);
+                my $pk_fields = join(":", $pk->fields);
+                next if $u_fields eq $pk_fields;
+              }
+
+              if ($name) {
+                # Force prepend of table_name as ORACLE doesn't allow duplicate
+                # CONSTRAINT names even for different tables (ORA-02264)
+                $name = mk_name( "${table_name}_$name", 'u' ) unless $name =~ /^$table_name/;
+              }
+              else {
                 $name = mk_name( $table_name, 'u' );
-                                                       }
-                                                       $name = quote($name, $qf);
+              }
+
+              $name = quote($name, $qf);
 
                 for my $f ( $c->fields ) {
                     my $field_def = $table->get_field( $f ) or next;
-                    my $dtype     = $translate{ $field_def->data_type } or next;
+                    my $dtype     = $translate{ ref $field_def->data_type eq "ARRAY" ? $field_def->data_type->[0] : $field_def->data_type} or next;
                     if ( $WARN && $dtype =~ /clob/i ) {
                         warn "Oracle will not allow UNIQUE constraints on " .
                              "CLOB field '" . $field_def->table->name . '.' .
@@ -419,12 +373,13 @@ sub create_table {
             }
             elsif ( $c->type eq CHECK_C ) {
                 $name ||= mk_name( $name || $table_name, 'ck' );
+                $name = quote($name, $qf);
                 my $expression = $c->expression || '';
                 push @constraint_defs, "CONSTRAINT $name CHECK ($expression)";
             }
             elsif ( $c->type eq FOREIGN_KEY ) {
                 $name = mk_name( join('_', $table_name, $c->fields). '_fk' );
-                                                               $name = quote($name, $qf);
+                $name = quote($name, $qf);
                 my $def = "CONSTRAINT $name FOREIGN KEY ";
 
                 if ( @fields ) {
@@ -492,25 +447,25 @@ sub create_table {
             if ( $index_type eq PRIMARY_KEY ) {
                 $index_name = $index_name ? mk_name( $index_name ) 
                     : mk_name( $table_name, 'pk' );
-                                                               $index_name = quote($index_name, $qf);
+                $index_name = quote($index_name, $qf);
                 push @field_defs, 'CONSTRAINT '.$index_name.' PRIMARY KEY '.
                     '(' . join( ', ', @fields ) . ')';
             }
             elsif ( $index_type eq NORMAL ) {
                 $index_name = $index_name ? mk_name( $index_name ) 
                     : mk_name( $table_name, $index_name || 'i' );
-                                                               $index_name = quote($index_name, $qf);
+                $index_name = quote($index_name, $qf);
                 push @index_defs, 
-                    "CREATE INDEX $index_name on ".quote($table_name,$qt)." (".
+                    "CREATE INDEX $index_name on $table_name_q (".
                         join( ', ', @fields ).  
                     ")$index_options";
             }
             elsif ( $index_type eq UNIQUE ) {
                 $index_name = $index_name ? mk_name( $index_name ) 
                     : mk_name( $table_name, $index_name || 'i' );
-                                                               $index_name = quote($index_name, $qf);
+                $index_name = quote($index_name, $qf);
                 push @index_defs, 
-                    "CREATE UNIQUE INDEX $index_name on $table_name (".
+                    "CREATE UNIQUE INDEX $index_name on $table_name_q (".
                         join( ', ', @fields ).  
                     ")$index_options"; 
             }
@@ -553,6 +508,7 @@ sub create_table {
 sub alter_field {
     my ($from_field, $to_field, $options) = @_;
 
+    my $qt = $options->{quote_table_names};
     my ($field_create, $field_defs, $trigger_defs, $field_comments) =
       create_field($to_field, $options, {});
 
@@ -563,7 +519,7 @@ sub alter_field {
         @$field_defs = map { s/ NOT NULL//; $_} @$field_defs;
     }
 
-    my $table_name = $to_field->table->name;
+    my $table_name = quote($to_field->table->name,$qt);
 
     return 'ALTER TABLE '.$table_name.' MODIFY ( '.join('', @$field_defs).' )';
 }
@@ -571,10 +527,11 @@ sub alter_field {
 sub add_field {
     my ($new_field, $options) = @_;
 
+    my $qt = $options->{quote_table_names};
     my ($field_create, $field_defs, $trigger_defs, $field_comments) =
       create_field($new_field, $options, {});
 
-    my $table_name = $new_field->table->name;
+    my $table_name = quote($new_field->table->name,$qt);
 
     my $out = sprintf('ALTER TABLE %s ADD ( %s )',
                       $table_name,
@@ -584,8 +541,8 @@ sub add_field {
 
 sub create_field {
     my ($field, $options, $field_name_scope) = @_;
-               my $qf = $options->{quote_field_names};
-               my $qt = $options->{quote_table_names};
+    my $qf = $options->{quote_field_names};
+    my $qt = $options->{quote_table_names};
 
     my (@create, @field_defs, @trigger_defs, @field_comments);
 
@@ -598,7 +555,7 @@ sub create_field {
     my $field_name    = mk_name(
                                 $field->name, '', $field_name_scope, 1
                                );
-               my $field_name_q = quote($field_name, $qf);
+    my $field_name_q = quote($field_name, $qf);
     my $field_def     = quote($field_name, $qf);
     $field->name( $field_name );
 
@@ -623,12 +580,16 @@ sub create_field {
         $data_type = 'varchar2';
     }
     else {
-        $data_type  = defined $translate{ $data_type } ?
-          $translate{ $data_type } :
-            $data_type;
-        $data_type ||= 'varchar2';
+      if (defined $translate{ $data_type }) {
+        if (ref $translate{ $data_type } eq "ARRAY") {
+          ($data_type,$size[0])  = @{$translate{ $data_type }};
+        } else {
+          $data_type  = $translate{ $data_type };
+        }
+      }
+      $data_type ||= 'varchar2';
     }
-    
+
     # ensure size is not bigger than max size oracle allows for data type
     if ( defined $max_size{$data_type} ) {
         for ( my $i = 0 ; $i < scalar @size ; $i++ ) {
@@ -665,6 +626,16 @@ sub create_field {
         undef @size;
     }
 
+    #
+    # Fixes ORA-00906: missing right parenthesis
+               # if size is 0 or undefined
+    #
+    for (qw/varchar2/) {
+        if ( $data_type =~ /^($_)$/i ) {
+            $size[0] ||= $max_size{$_};
+        }
+    }
+
     $field_def .= " $data_type";
     if ( defined $size[0] && $size[0] > 0 ) {
         $field_def .= '(' . join( ',', @size ) . ')';
@@ -743,7 +714,7 @@ sub create_field {
           " INTO :new." . $field_name_q."\n" .
           " FROM dual;\n" .
           "END;\n";
-        
+
         push @trigger_defs, $trigger;
     }
 
@@ -766,7 +737,7 @@ sub create_field {
     if ( my $comment = $field->comments ) {
         $comment =~ s/'/''/g;
         push @field_comments, 
-          "COMMENT ON COLUMN $table_name.$field_name is\n '" .
+          "COMMENT ON COLUMN $table_name_q.$field_name_q is\n '" .
             $comment . "';" unless $options->{no_comments};
     }
 
@@ -777,8 +748,7 @@ sub create_field {
 
 sub create_view {
     my ($view, $options) = @_;
-               my $qt = $options->{quote_table_names};
-               my $qf = $options->{quote_field_names};
+    my $qt = $options->{quote_table_names};
     my $view_name = quote($view->name,$qt);
     
     my @create;
@@ -836,16 +806,7 @@ sub mk_name {
 # -------------------------------------------------------------------
 sub quote {
   my ($name, $q) = @_;
-       if ( $q ) {
-                       "$q$name$q";
-       } elsif ($ora_reserved { uc $name }) {
-               # convert to upper case to be consistent with oracle
-               # when no quotes are being used
-               $name = uc $name;
-               "$quote_char$name$quote_char";
-       } else {
-               $name;
-       }
+  $q && $name ? "$quote_char$name$quote_char" : $name;
 }