Rolled in Darren's new list_[producers|parsers], lots of cosmetic changes,
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Oracle.pm
CommitLineData
16dc9970 1package SQL::Translator::Producer::Oracle;
2
077ebf34 3# -------------------------------------------------------------------
d529894e 4# $Id: Oracle.pm,v 1.4 2002-11-22 03:03:40 kycl4rk Exp $
077ebf34 5# -------------------------------------------------------------------
d529894e 6# Copyright (C) 2002 Ken Y. Clark <kclark@cpan.org>,
077ebf34 7# darren chamberlain <darren@cpan.org>
16dc9970 8#
077ebf34 9# This program is free software; you can redistribute it and/or
10# modify it under the terms of the GNU General Public License as
11# published by the Free Software Foundation; version 2.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16# General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21# 02111-1307 USA
22# -------------------------------------------------------------------
23
16dc9970 24
25use strict;
d529894e 26use vars qw[ $VERSION $DEBUG ];
27$VERSION = sprintf "%d.%02d", q$Revision: 1.4 $ =~ /(\d+)\.(\d+)/;
28$DEBUG = 0 unless defined $DEBUG;
16dc9970 29
d529894e 30my $max_id_length = 30;
16dc9970 31my %used_identifiers = ();
32
33my %translate = (
d529894e 34 #
35 # MySQL types
36 #
16dc9970 37 bigint => 'number',
38 double => 'number',
39 decimal => 'number',
40 float => 'number',
41 int => 'number',
42 mediumint => 'number',
43 smallint => 'number',
44 tinyint => 'number',
16dc9970 45 char => 'char',
16dc9970 46 varchar => 'varchar2',
16dc9970 47 tinyblob => 'CLOB',
48 blob => 'CLOB',
49 mediumblob => 'CLOB',
50 longblob => 'CLOB',
16dc9970 51 longtext => 'long',
52 mediumtext => 'long',
53 text => 'long',
54 tinytext => 'long',
16dc9970 55 enum => 'varchar2',
56 set => 'varchar2',
16dc9970 57 date => 'date',
58 datetime => 'date',
59 time => 'date',
60 timestamp => 'date',
61 year => 'date',
d529894e 62
63 #
64 # PostgreSQL types
65 #
66 smallint => '',
67 integer => '',
68 bigint => '',
69 decimal => '',
70 numeric => '',
71 real => '',
72 'double precision' => '',
73 serial => '',
74 bigserial => '',
75 money => '',
76 character => '',
77 'character varying' => '',
78 bytea => '',
79 interval => '',
80 boolean => '',
81 point => '',
82 line => '',
83 lseg => '',
84 box => '',
85 path => '',
86 polygon => '',
87 circle => '',
88 cidr => '',
89 inet => '',
90 macaddr => '',
91 bit => '',
92 'bit varying' => '',
93);
94
95#
96# Oracle reserved words from:
97# http://technet.oracle.com/docs/products/oracle8i/doc_library/\
98# 817_doc/server.817/a85397/ap_keywd.htm
99#
100my @ora_reserved = qw(
101 ACCESS ADD ALL ALTER AND ANY AS ASC AUDIT
102 BETWEEN BY
103 CHAR CHECK CLUSTER COLUMN COMMENT COMPRESS CONNECT CREATE CURRENT
104 DATE DECIMAL DEFAULT DELETE DESC DISTINCT DROP
105 ELSE EXCLUSIVE EXISTS
106 FILE FLOAT FOR FROM
107 GRANT GROUP
108 HAVING
109 IDENTIFIED IMMEDIATE IN INCREMENT INDEX INITIAL INSERT
110 INTEGER INTERSECT INTO IS
111 LEVEL LIKE LOCK LONG
112 MAXEXTENTS MINUS MLSLABEL MODE MODIFY
113 NOAUDIT NOCOMPRESS NOT NOWAIT NULL NUMBER
114 OF OFFLINE ON ONLINE OPTION OR ORDER
115 PCTFREE PRIOR PRIVILEGES PUBLIC
116 RAW RENAME RESOURCE REVOKE ROW ROWID ROWNUM ROWS
117 SELECT SESSION SET SHARE SIZE SMALLINT START
118 SUCCESSFUL SYNONYM SYSDATE
119 TABLE THEN TO TRIGGER
120 UID UNION UNIQUE UPDATE USER
121 VALIDATE VALUES VARCHAR VARCHAR2 VIEW
122 WHENEVER WHERE WITH
16dc9970 123);
124
d529894e 125my %ora_reserved = map { $_ => 1 } @ora_reserved;
126my %global_names;
127my %unreserve;
128my %truncated;
16dc9970 129
077ebf34 130sub produce {
131 my ( $translator, $data ) = @_;
d529894e 132 $DEBUG = $translator->debug;
133 my $no_comments = $translator->no_comments;
16dc9970 134
135 #print "got ", scalar keys %$data, " tables:\n";
136 #print join(', ', keys %$data), "\n";
137 #print Dumper( $data );
138
d529894e 139 my $output;
140 unless ( $no_comments ) {
141 $output .= sprintf
142 "--\n-- Created by %s\n-- Created on %s\n--\n\n",
143 __PACKAGE__, scalar localtime;
144 }
077ebf34 145
d529894e 146 if ( $translator->parser_type =~ /mysql/i ) {
147 $output .=
148 "-- We assume that default NLS_DATE_FORMAT has been changed\n".
149 "-- but we set it here anyway to be self-consistent.\n".
150 "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';\n\n";
151 }
16dc9970 152
153 #
154 # Print create for each table
155 #
156 my ( $index_i, $trigger_i ) = ( 1, 1 );
d529894e 157 for my $table (
158 # sort keys %$data
159 map { $_->[1] }
160 sort { $a->[0] <=> $b->[0] }
161 map { [ $_->{'order'}, $_ ] }
162 values %{ $data }
163 ) {
164 my $table_name = $table->{'table_name'};
165# check_identifier( $table_name );
166 $table_name = mk_name( $table_name, '', undef, 1 );
167# my $tablename_ur = unreserve($table_name);
16dc9970 168
169 my ( @comments, @field_decs, @trigger_decs );
170
d529894e 171 push @comments, "--\n-- Table: $table_name\n--" unless $no_comments;
16dc9970 172
173 for my $field (
174 map { $_->[1] }
175 sort { $a->[0] <=> $b->[0] }
176 map { [ $_->{'order'}, $_ ] }
177 values %{ $table->{'fields'} }
178 ) {
179 #
180 # Field name
181 #
182 my $field_str = check_identifier( $field->{'name'} );
183
184 #
185 # Datatype
186 #
187 my $data_type = $field->{'data_type'};
188 $data_type = defined $translate{ $data_type } ?
189 $translate{ $data_type } :
190 die "Unknown datatype: $data_type\n";
191 $field_str .= ' '.$data_type;
d529894e 192 $field_str .= '('.join(',', @{ $field->{'size'} }).')'
193 if @{ $field->{'size'} || [] };
16dc9970 194
195 #
196 # Default value
197 #
198 if ( $field->{'default'} ) {
199 # next if $field->{'default'} eq 'NULL';
200 $field_str .= sprintf(
201 ' DEFAULT %s',
202 $field->{'default'} =~ m/null/i ? 'NULL' :
203 "'".$field->{'default'}."'"
204 );
205 }
206
207 #
208 # Not null constraint
209 #
210 unless ( $field->{'null'} ) {
211 my $constraint_name = make_identifier($field->{'name'}, '_nn');
212 $field_str .= ' CONSTRAINT ' . $constraint_name . ' NOT NULL';
213 }
214
215 #
216 # Auto_increment
217 #
218 if ( $field->{'is_auto_inc'} ) {
219 my $trigger_no = $trigger_i++;
220 my $trigger_sequence =
221 join( '_', 'seq' , $field->{'name'}, $trigger_no );
222 my $trigger_name =
223 join( '_', 'autoinc', $field->{'name'}, $trigger_no );
224
225 push @trigger_decs,
d529894e 226 "CREATE SEQUENCE $trigger_sequence;\n" .
227 "CREATE OR REPLACE TRIGGER $trigger_name\n" .
228 "BEFORE INSERT ON $table_name\n" .
229 "FOR EACH ROW WHEN (new.".$field->{'name'}." is null)\n".
230 "BEGIN\n" .
231 " SELECT $trigger_sequence.nextval\n" .
232 " INTO :new." . $field->{'name'}."\n" .
16dc9970 233 " FROM dual;\n" .
d529894e 234 " END $trigger_name;/"
16dc9970 235 ;
236 }
237
238 push @field_decs, $field_str;
239 }
240
241 #
242 # Index Declarations
243 #
244 my @index_decs = ();
49e1eb70 245 for my $index ( @{ $table->{'indices'} } ) {
16dc9970 246 my $index_name = $index->{'name'} || '';
247 my $index_type = $index->{'type'} || 'normal';
248 my @fields = @{ $index->{'fields'} } or next;
249
250 if ( $index_type eq 'primary_key' ) {
251 if ( !$index_name ) {
252 $index_name = make_identifier( $table_name, 'i_', '_pk' );
253 }
254 elsif ( $index_name !~ m/^i_/ ) {
255 $index_name = make_identifier( $table_name, 'i_' );
256 }
257 elsif ( $index_name !~ m/_pk$/ ) {
258 $index_name = make_identifier( $table_name, '_pk' );
259 }
260 else {
261 $index_name = make_identifier( $index_name );
262 }
263
264 push @field_decs, 'CONSTRAINT ' . $index_name . ' PRIMARY KEY ' .
265 '(' . join( ', ', @fields ) . ')';
266 }
267
268 elsif ( $index_type eq 'unique' ) {
269 if ( !$index_name ) {
270 $index_name = make_identifier( join( '_', @fields ), 'u_' );
271 }
272 elsif ( $index_name !~ m/^u_/ ) {
273 $index_name = make_identifier( $index_name, 'u_' );
274 }
275 else {
276 $index_name = make_identifier( $index_name );
277 }
278
279 push @field_decs, 'CONSTRAINT ' . $index_name . ' UNIQUE ' .
280 '(' . join( ', ', @fields ) . ')';
281 }
282
283 elsif ( $index_type eq 'normal' ) {
284 if ( !$index_name ) {
285 $index_name =
286 make_identifier($table_name, 'i_', '_'.$index_i++ );
287 }
288 elsif ( $index_name !~ m/^i_/ ) {
289 $index_name = make_identifier( $index_name, 'i_' );
290 }
291 else {
292 $index_name = make_identifier( $index_name );
293 }
294
295 push @index_decs, "CREATE INDEX $index_name on $table_name (".
296 join( ', ', @{ $index->{'fields'} } ).
297 ");"
298 ;
299 }
300
301 else {
302 warn "On table $table_name, unknown index type: $index_type\n";
303 }
304 }
305
306 my $create_statement = "CREATE TABLE $table_name (\n".
307 join( ",\n", map { " $_" } @field_decs ).
308 "\n);"
309 ;
310
311 $output .= join( "\n\n",
312 @comments,
313 $create_statement,
314 @trigger_decs,
315 @index_decs,
316 ''
317 );
318 }
319
d529894e 320 return $output;
16dc9970 321}
322
323#
324# Used to make index names
325#
326sub make_identifier {
327 my ( $identifier, @mutations ) = @_;
328 my $length_of_mutations;
329 for my $mutation ( @mutations ) {
330 $length_of_mutations += length( $mutation );
331 }
332
333 if (
334 length( $identifier ) + $length_of_mutations >
d529894e 335 $max_id_length
16dc9970 336 ) {
337 $identifier = substr(
338 $identifier,
339 0,
d529894e 340 $max_id_length - $length_of_mutations
16dc9970 341 );
342 }
343
344 for my $mutation ( @mutations ) {
345 if ( $mutation =~ m/.+_$/ ) {
346 $identifier = $mutation.$identifier;
347 }
348 elsif ( $mutation =~ m/^_.+/ ) {
349 $identifier = $identifier.$mutation;
350 }
351 }
352
353 if ( $used_identifiers{ $identifier } ) {
354 my $index = 1;
355 if ( $identifier =~ m/_(\d+)$/ ) {
356 $index = $1;
357 $identifier = substr(
358 $identifier,
359 0,
360 length( $identifier ) - ( length( $index ) + 1 )
361 );
362 }
363 $index++;
364 return make_identifier( $identifier, '_'.$index );
365 }
366
367 $used_identifiers{ $identifier } = 1;
368
369 return $identifier;
370}
371
372#
373# Checks to see if an identifier is not too long
374#
375sub check_identifier {
376 my $identifier = shift;
377 die "Identifier '$identifier' is too long, unrecoverable error.\n"
d529894e 378 if length( $identifier ) > $max_id_length;
16dc9970 379 return $identifier;
380}
381
d529894e 382# -------------------------------------------------------------------
383sub mk_name {
384 my ($basename, $type, $scope, $critical) = @_;
385 my $basename_orig = $basename;
386 my $max_name = $max_id_length - (length($type) + 1);
387 $basename = substr($basename, 0, $max_name)
388 if length($basename) > $max_name;
389 my $name = $type ? "${type}_$basename" : $basename;
390
391 if ( $basename ne $basename_orig and $critical ) {
392 my $show_type = $type ? "+'$type'" : "";
393 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
394 "character limit to make '$name'\n" if $DEBUG;
395 $truncated{$basename_orig} = $name;
396 }
397
398 $scope ||= \%global_names;
399 return $name unless $scope->{$name}++;
400 my $name_orig = $name;
401 $name .= "02";
402 substr($name, $max_id_length - 3) = "00" if length($name) > $max_id_length;
403 ++$name while $scope->{$name};
404 warn "The name '$name_orig' has been changed to ",
405 "'$name' to make it unique\n" if $DEBUG;
406 return $name;
407}
408
409# -------------------------------------------------------------------
410sub unreserve {
411 my ($name, $schema_obj_name) = @_;
412 my ($suffix) = ($name =~ s/(\W.*)$//) ? $1 : '';
413
414 # also trap fields that don't begin with a letter
415 return $_[0] if !$ora_reserved{uc $name}
416 && $name =~ /^[a-z]/i;
417
418 if ( $schema_obj_name ) {
419 ++$unreserve{"$schema_obj_name.$name"};
420 }
421 else {
422 ++$unreserve{"$name (table name)"};
423 }
424
425 my $unreserve = sprintf '%s_', $name;
426 return $unreserve.$suffix;
427}
428
16dc9970 4291;
430
d529894e 431# -------------------------------------------------------------------
16dc9970 432# All bad art is the result of good intentions.
433# Oscar Wilde
d529894e 434# -------------------------------------------------------------------
16dc9970 435
436=head1 NAME
437
438SQL::Translator::Producer::Oracle - Oracle SQL producer
439
440=head1 SYNOPSIS
441
077ebf34 442 use SQL::Translator::Parser::MySQL;
16dc9970 443 use SQL::Translator::Producer::Oracle;
444
077ebf34 445 my $original_create = ""; # get this from somewhere...
446 my $translator = SQL::Translator->new;
447
448 $translator->parser("SQL::Translator::Parser::MySQL");
449 $translator->producer("SQL::Translator::Producer::Oracle");
450
451 my $new_create = $translator->translate($original_create);
452
16dc9970 453=head1 DESCRIPTION
454
077ebf34 455SQL::Translator::Producer::Oracle takes a parsed data structure,
456created by a SQL::Translator::Parser subclass, and turns it into a
457create string suitable for use with an Oracle database.
16dc9970 458
efd49776 459=head1 BUGS
460
461Problem with SQL::Translator::Producer::Oracle: it is keeping track
462of the last sequence number used, so as not to duplicate them, which
463is reasonable. However on runs past the first, it seems to be
464creating multiple constraint lines, that look like:
465
466 CONSTRAINT i_sessions_pk_2 PRIMARY KEY (id),
467 CONSTRAINT i_sessions_pk_3 PRIMARY KEY (id)
468
469This is a very preliminary finding, and needs to be investigated more
470thoroughly, of course.
471
d529894e 472=head1 CREDITS
473
474A hearty "thank-you" to Tim Bunce for much of the logic stolen from
475his "mysql2ora" script.
16dc9970 476
477=head1 AUTHOR
478
d529894e 479Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
16dc9970 480
481=head1 SEE ALSO
482
483perl(1).
484
485=cut