package SQL::Translator::Producer::Oracle;
# -------------------------------------------------------------------
-# $Id: Oracle.pm,v 1.21 2003-08-19 14:44:00 kycl4rk Exp $
+# $Id: Oracle.pm,v 1.33 2005-05-25 15:17:49 mwz444 Exp $
# -------------------------------------------------------------------
-# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
-# darren chamberlain <darren@cpan.org>,
-# Chris Mungall <cjm@fruitfly.org>
+# Copyright (C) 2002-4 SQLFairy Authors
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# 02111-1307 USA
# -------------------------------------------------------------------
+=head1 NAME
+
+SQL::Translator::Producer::Oracle - Oracle SQL producer
+
+=head1 SYNOPSIS
+
+ use SQL::Translator;
+
+ my $t = SQL::Translator->new( parser => '...', producer => 'Oracle' );
+ print $translator->translate( $file );
+
+=head1 DESCRIPTION
+
+Creates an SQL DDL suitable for Oracle.
+
+=cut
+
use strict;
use vars qw[ $VERSION $DEBUG $WARN ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.21 $ =~ /(\d+)\.(\d+)/;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.33 $ =~ /(\d+)\.(\d+)/;
$DEBUG = 0 unless defined $DEBUG;
use SQL::Translator::Schema::Constants;
macaddr => 'varchar2',
bit => 'number',
'bit varying' => 'number',
+
+ #
+ # Oracle types
+ #
+ number => 'number',
+ varchar2 => 'varchar2',
+ long => 'clob',
);
#
if ( $translator->parser_type =~ /mysql/i ) {
$output .=
- "-- We assume that default NLS_DATE_FORMAT has been changed\n".
- "-- but we set it here anyway to be self-consistent.\n".
+ "-- We assume that default NLS_DATE_FORMAT has been changed\n".
+ "-- but we set it here anyway to be self-consistent.\n"
+ unless $no_comments;
+
+ $output .=
"ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';\n\n";
}
);
my $field_name_ur = unreserve( $field_name, $table_name );
my $field_def = $field_name_ur;
+ $field->name( $field_name_ur );
#
# Datatype
else {
$data_type = defined $translate{ $data_type } ?
$translate{ $data_type } :
- die "Unknown datatype: $data_type\n";
+ $data_type;
+ $data_type ||= 'varchar2';
}
#
if $WARN;
}
+ if ( $data_type eq 'clob' && $field->is_unique ) {
+ $data_type = 'varchar2';
+ $size[0] = 4000;
+ warn "CLOB cannot be a unique key, changing to VARCHAR2\n"
+ if $WARN;
+ }
+
+
+
#
# Fixes ORA-00907: missing right parenthesis
#
#
my $default = $field->default_value;
if ( defined $default ) {
- $field_def .= sprintf(
- ' DEFAULT %s',
- $default =~ m/null/i ? 'NULL' : "'$default'"
- );
+ #
+ # Wherein we try to catch a string being used as
+ # a default value for a numerical field. If "true/false,"
+ # then sub "1/0," otherwise just test the truthity of the
+ # argument and use that (naive?).
+ #
+ if (
+ $data_type =~ /^number$/i &&
+ $default !~ /^\d+$/ &&
+ $default !~ m/null/i
+ ) {
+ if ( $default =~ /^true$/i ) {
+ $default = "'1'";
+ }
+ elsif ( $default =~ /^false$/i ) {
+ $default = "'0'";
+ }
+ else {
+ $default = $default ? "'1'" : "'0'";
+ }
+ }
+ elsif (
+ $data_type =~ /date/ && (
+ $default eq 'current_timestamp'
+ ||
+ $default eq 'now()'
+ )
+ ) {
+ $default = 'SYSDATE';
+ }
+ else {
+ $default = $default =~ m/null/i ? 'NULL' : "'$default'"
+ }
+
+ $field_def .= " DEFAULT $default",
}
#
# Not null constraint
#
unless ( $field->is_nullable ) {
- my $constraint_name = mk_name($field_name_ur, 'nn');
- $field_def .= ' CONSTRAINT ' . $constraint_name . ' NOT NULL';
+ $field_def .= ' NOT NULL';
}
$field_def .= " $check" if $check;
# Auto_increment
#
if ( $field->is_auto_increment ) {
- my $base_name = $table_name . "_". $field_name;
+ my $base_name = $table_name_ur . "_". $field_name;
my $seq_name = mk_name( $base_name, 'sq' );
my $trigger_name = mk_name( $base_name, 'ai' );
push @trigger_defs,
"CREATE SEQUENCE $seq_name;\n" .
"CREATE OR REPLACE TRIGGER $trigger_name\n" .
- "BEFORE INSERT ON $table_name\n" .
+ "BEFORE INSERT ON $table_name_ur\n" .
"FOR EACH ROW WHEN (\n" .
" new.$field_name_ur IS NULL".
" OR new.$field_name_ur = 0\n".
}
if ( lc $field->data_type eq 'timestamp' ) {
- my $base_name = $table_name . "_". $field_name_ur;
+ my $base_name = $table_name_ur . "_". $field_name_ur;
my $trig_name = mk_name( $base_name, 'ts' );
push @trigger_defs,
"CREATE OR REPLACE TRIGGER $trig_name\n".
push @field_defs, $field_def;
if ( my $comment = $field->comments ) {
+ $comment =~ s/'/''/g;
push @field_comments,
- "COMMENT ON COLUMN $table_name.$field_name_ur is\n '".
- $comment."';";
+ "COMMENT ON COLUMN $table_name_ur.$field_name_ur is\n '" .
+ $comment . "';" unless $no_comments;
+ }
+ }
+
+ #
+ # Table options
+ #
+ my @table_options;
+ for my $opt ( $table->options ) {
+ if ( ref $opt eq 'HASH' ) {
+ my ( $key, $value ) = each %$opt;
+ if ( ref $value eq 'ARRAY' ) {
+ push @table_options, "$key\n(\n". join ("\n",
+ map { " $_->[0]\t$_->[1]" }
+ map { [ each %$_ ] }
+ @$value
+ )."\n)";
+ }
+ elsif ( !defined $value ) {
+ push @table_options, $key;
+ }
+ else {
+ push @table_options, "$key $value";
+ }
}
}
#
# Table constraints
#
- my $constraint_name_default;
for my $c ( $table->get_constraints ) {
my $name = $c->name || '';
my @fields = map { unreserve( $_, $table_name ) } $c->fields;
my @rfields = map { unreserve( $_, $table_name ) }
$c->reference_fields;
- next unless @fields;
+ next if !@fields && $c->type ne CHECK_C;
if ( $c->type eq PRIMARY_KEY ) {
$name ||= mk_name( $table_name, 'pk' );
'(' . join( ', ', @fields ) . ')';
}
elsif ( $c->type eq UNIQUE ) {
- $name ||= mk_name( $table_name, ++$constraint_name_default );
+ $name = mk_name( $name || $table_name, 'u' );
+
+ for my $f ( $c->fields ) {
+ my $field_def = $table->get_field( $f ) or next;
+ my $dtype = $translate{ $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 . '.' .
+ $field_def->name . ".'\n"
+ }
+ }
+
push @constraint_defs, "CONSTRAINT $name UNIQUE " .
'(' . join( ', ', @fields ) . ')';
}
+ elsif ( $c->type eq CHECK_C ) {
+ $name = mk_name( $name || $table_name, 'ck' );
+ my $expression = $c->expression || '';
+ push @constraint_defs, "CONSTRAINT $name CHECK ($expression)";
+ }
elsif ( $c->type eq FOREIGN_KEY ) {
- $name ||= mk_name( $table_name, ++$constraint_name_default );
+ $name = mk_name( join('_', $table_name, $c->fields), 'fk' );
my $def = "CONSTRAINT $name FOREIGN KEY ";
if ( @fields ) {
- $def .= join( ', ', @fields );
+ $def .= '(' . join( ', ', @fields ) . ')';
}
- $def .= ' REFERENCES ' . $c->reference_table;
+ my $ref_table = unreserve($c->reference_table);
+
+ $def .= " REFERENCES $ref_table";
if ( @rfields ) {
$def .= ' (' . join( ', ', @rfields ) . ')';
# Index Declarations
#
my @index_defs = ();
- my $idx_name_default;
for my $index ( $table->get_indices ) {
my $index_name = $index->name || '';
my $index_type = $index->type || NORMAL;
next unless @fields;
if ( $index_type eq PRIMARY_KEY ) {
- $index_name = mk_name( $table_name, 'pk' );
+ $index_name = $index_name ? mk_name( $index_name )
+ : mk_name( $table_name, 'pk' );
push @field_defs, 'CONSTRAINT '.$index_name.' PRIMARY KEY '.
'(' . join( ', ', @fields ) . ')';
}
- elsif ( $index_type eq UNIQUE ) {
- $index_name = mk_name(
- $table_name, $index_name || ++$idx_name_default
- );
- push @field_defs, 'CONSTRAINT ' . $index_name . ' UNIQUE ' .
- '(' . join( ', ', @fields ) . ')';
- }
-
elsif ( $index_type eq NORMAL ) {
- $index_name = mk_name(
- $table_name, $index_name || ++$idx_name_default
- );
+ $index_name = $index_name ? mk_name( $index_name )
+ : mk_name( $table_name, $index_name || 'i' );
push @index_defs,
"CREATE INDEX $index_name on $table_name_ur (".
join( ', ', @fields ).
if ( my @table_comments = $table->comments ) {
for my $comment ( @table_comments ) {
next unless $comment;
- push @field_comments, "COMMENT ON TABLE $table_name is\n '".
- $comment."';"
+ $comment =~ s/'/''/g;
+ push @field_comments, "COMMENT ON TABLE $table_name_ur is\n '".
+ $comment . "';" unless $no_comments
;
}
}
+ my $table_options = @table_options
+ ? "\n".join("\n", @table_options) : '';
$create_statement .= "CREATE TABLE $table_name_ur (\n" .
join( ",\n", map { " $_" } @field_defs, @constraint_defs ) .
- "\n);"
+ "\n)$table_options;"
;
$output .= join( "\n\n",
sub mk_name {
my $basename = shift || '';
my $type = shift || '';
+ $type = '' if $type =~ /^\d/;
my $scope = shift || '';
my $critical = shift || '';
my $basename_orig = $basename;
$scope ||= \%global_names;
if ( my $prev = $scope->{ $name } ) {
my $name_orig = $name;
- $name .= sprintf( "%02d", ++$prev );
- substr($name, $max_id_length - 3) = "00"
- if length( $name ) > $max_id_length;
+ substr($name, $max_id_length - 2) = ""
+ if length( $name ) >= $max_id_length - 1;
+ $name .= sprintf( "%02d", $prev++ );
warn "The name '$name_orig' has been changed to ",
"'$name' to make it unique.\n" if $WARN;
# Oscar Wilde
# -------------------------------------------------------------------
-=head1 NAME
-
-SQL::Translator::Producer::Oracle - Oracle SQL producer
-
-=head1 SYNOPSIS
-
- use SQL::Translator::Parser::MySQL;
- use SQL::Translator::Producer::Oracle;
-
- my $original_create = ""; # get this from somewhere...
- my $translator = SQL::Translator->new;
-
- $translator->parser("SQL::Translator::Parser::MySQL");
- $translator->producer("SQL::Translator::Producer::Oracle");
-
- my $new_create = $translator->translate($original_create);
-
-=head1 DESCRIPTION
-
-SQL::Translator::Producer::Oracle takes a parsed data structure,
-created by a SQL::Translator::Parser subclass, and turns it into a
-create string suitable for use with an Oracle database.
+=pod
=head1 CREDITS
-A hearty "thank-you" to Tim Bunce for much of the logic stolen from
-his "mysql2ora" script.
+Mad props to Tim Bunce for much of the logic stolen from his "mysql2ora"
+script.
=head1 AUTHOR
-Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
+Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
=head1 SEE ALSO
-perl(1).
+SQL::Translator, DDL::Oracle, mysql2ora.
=cut