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