File Coverage

blib/lib/SQL/Translator/Producer/Oracle.pm
Criterion Covered Total %
statement 319 355 89.8
branch 145 214 67.7
condition 64 100 64.0
subroutine 22 22 100.0
pod 9 16 56.2
total 559 707 79.0


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