File Coverage

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


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   4737 use strict;
  12         25  
  12         406  
91 12     12   78 use warnings;
  12         37  
  12         835  
92             our ( $DEBUG, $WARN );
93             our $VERSION = '1.63';
94             $DEBUG = 0 unless defined $DEBUG;
95              
96 12     12   102 use base 'SQL::Translator::Producer';
  12         33  
  12         2033  
97 12     12   85 use SQL::Translator::Schema::Constants;
  12         83  
  12         931  
98 12     12   79 use SQL::Translator::Utils qw(debug header_comment);
  12         31  
  12         729  
99 12     12   794 use Data::Dumper;
  12         7074  
  12         61609  
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 7     7 1 21 my $translator = shift;
191 7         30 $DEBUG = $translator->debug;
192 7   50     384 $WARN = $translator->show_warnings || 0;
193 7         234 my $no_comments = $translator->no_comments;
194 7         182 my $add_drop_table = $translator->add_drop_table;
195 7         206 my $schema = $translator->schema;
196 7   50     205 my $oracle_version = $translator->producer_args->{oracle_version} || 0;
197 7         154 my $delay_constraints = $translator->producer_args->{delay_constraints};
198 7         29 my ($output, $create, @table_defs, @fk_defs, @trigger_defs, @index_defs, @constraint_defs);
199              
200 7         33 debug("ORA: Beginning production");
201 7 100       50 $create .= header_comment unless ($no_comments);
202 7 100       52 my $qt = 1 if $translator->quote_table_names;
203 7 100       104 my $qf = 1 if $translator->quote_field_names;
204              
205 7 100       217 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 .=
212             "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';\n\n";
213             }
214              
215 7         49 for my $table ( $schema->get_tables ) {
216 13         290 debug("ORA: Producing for table " . $table->name);
217 13         159 my ( $table_def, $fk_def, $trigger_def, $index_def, $constraint_def ) = create_table(
218             $table,
219             {
220             add_drop_table => $add_drop_table,
221             show_warnings => $WARN,
222             no_comments => $no_comments,
223             delay_constraints => $delay_constraints,
224             quote_table_names => $qt,
225             quote_field_names => $qf,
226             }
227             );
228 13         63 push @table_defs, @$table_def;
229 13         33 push @fk_defs, @$fk_def;
230 13         28 push @trigger_defs, @$trigger_def;
231 13         25 push @index_defs, @$index_def;
232 13         46 push @constraint_defs, @$constraint_def;
233             }
234              
235 7         34 my (@view_defs);
236 7         68 foreach my $view ( $schema->get_views ) {
237 1         8 my ( $view_def ) = create_view(
238             $view,
239             {
240             add_drop_view => $add_drop_table,
241             quote_table_names => $qt,
242             }
243             );
244 1         5 push @view_defs, @$view_def;
245             }
246              
247 7 100       30 if (wantarray) {
248 1 50       10 return defined $create ? $create : (), @table_defs, @view_defs, @fk_defs, @trigger_defs, @index_defs, @constraint_defs;
249             }
250             else {
251 6         69 $create .= join (";\n\n", @table_defs, @view_defs, @fk_defs, @index_defs, @constraint_defs);
252 6         20 $create .= ";\n\n";
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             $create .= "$_/\n\n"
257 6         30 for @trigger_defs;
258 6         510 return $create;
259             }
260             }
261              
262             sub create_table {
263 14     14 1 66 my ($table, $options) = @_;
264 14         46 my $qt = $options->{quote_table_names};
265 14         42 my $qf = $options->{quote_field_names};
266 14         338 my $table_name = $table->name;
267 14         566 my $table_name_q = quote($table_name,$qt);
268              
269 14         62 my $item = '';
270 14         39 my $drop;
271 14         47 my (@create, @field_defs, @constraint_defs, @fk_defs, @trigger_defs);
272              
273 14 100       99 push @create, "--\n-- Table: $table_name\n--" unless $options->{no_comments};
274 14 100       115 push @create, qq[DROP TABLE $table_name_q CASCADE CONSTRAINTS] if $options->{add_drop_table};
275              
276 14         52 my ( %field_name_scope, @field_comments );
277 14         84 for my $field ( $table->get_fields ) {
278 53         1105 debug("ORA: Creating field " . $field->name . "(" . $field->data_type . ")");
279 53         249 my ($field_create, $field_defs, $trigger_defs, $field_comments) =
280             create_field($field, $options, \%field_name_scope);
281 53 50       218 push @create, @$field_create if ref $field_create;
282 53 50       172 push @field_defs, @$field_defs if ref $field_defs;
283 53 50       158 push @trigger_defs, @$trigger_defs if ref $trigger_defs;
284 53 50       210 push @field_comments, @$field_comments if ref $field_comments;
285             }
286              
287             #
288             # Table options
289             #
290 14         54 my @table_options;
291 14         355 for my $opt ( $table->options ) {
292 7 100       43 if ( ref $opt eq 'HASH' ) {
293 5         35 my ( $key, $value ) = each %$opt;
294 5 50       29 if ( ref $value eq 'ARRAY' ) {
    50          
295             push @table_options, "$key\n(\n". join ("\n",
296 0         0 map { " $_->[0]\t$_->[1]" }
297 0         0 map { [ each %$_ ] }
  0         0  
298             @$value
299             )."\n)";
300             }
301             elsif ( !defined $value ) {
302 0         0 push @table_options, $key;
303             }
304             else {
305 5         26 push @table_options, "$key $value";
306             }
307             }
308             }
309              
310             #
311             # Table constraints
312             #
313 14         72 for my $c ( $table->get_constraints ) {
314 16         217 my $constr = create_constraint($c, $options);
315 16 50       65 if ($constr) {
316 16 100       370 if ($c->type eq FOREIGN_KEY) { # FK defs always come later as alters
317 3         89 push @fk_defs, sprintf("ALTER TABLE %s ADD %s", $table_name_q, $constr);
318             }
319             else {
320 13         323 push @constraint_defs, $constr;
321             }
322             }
323             }
324              
325             #
326             # Index Declarations
327             #
328 14         71 my @index_defs = ();
329 14         71 for my $index ( $table->get_indices ) {
330 7   50     249 my $index_name = $index->name || '';
331 7   50     223 my $index_type = $index->type || NORMAL;
332 7         319 my @fields = map { quote($_, $qf) } $index->fields;
  8         31  
333 7 50       38 next unless @fields;
334 7         82 debug("ORA: Creating $index_type index on fields (" . join(', ', @fields) . ") named $index_name");
335 7         19 my @index_options;
336 7         183 for my $opt ( $index->options ) {
337 3 50       15 if ( ref $opt eq 'HASH' ) {
338 3         16 my ( $key, $value ) = each %$opt;
339 3 50       17 if ( ref $value eq 'ARRAY' ) {
    50          
340             push @table_options, "$key\n(\n". join ("\n",
341 0         0 map { " $_->[0]\t$_->[1]" }
342 0         0 map { [ each %$_ ] }
  0         0  
343             @$value
344             )."\n)";
345             }
346             elsif ( !defined $value ) {
347 0         0 push @index_options, $key;
348             }
349             else {
350 3         16 push @index_options, "$key $value";
351             }
352             }
353             }
354 7 100       36 my $index_options = @index_options
355             ? "\n".join("\n", @index_options) : '';
356              
357 7 50 66     67 if ( $index_type eq PRIMARY_KEY ) {
    50          
358 0 0       0 $index_name = $index_name ? mk_name( $index_name )
359             : mk_name( $table_name, 'pk' );
360 0         0 $index_name = quote($index_name, $qf);
361 0         0 push @field_defs, 'CONSTRAINT '.$index_name.' PRIMARY KEY '.
362             '(' . join( ', ', @fields ) . ')';
363             }
364             elsif ($index_type eq NORMAL or $index_type eq UNIQUE) {
365 7         50 push @index_defs, create_index($index, $options, $index_options);
366             }
367             else {
368 0 0       0 warn "Unknown index type ($index_type) on table $table_name.\n"
369             if $WARN;
370             }
371             }
372              
373 14 50       384 if ( my @table_comments = $table->comments ) {
374 0         0 for my $comment ( @table_comments ) {
375 0 0       0 next unless $comment;
376 0         0 $comment = __PACKAGE__->_quote_string($comment);
377             push @field_comments, "COMMENT ON TABLE $table_name_q is\n $comment"
378 0 0       0 unless $options->{no_comments};
379             }
380             }
381              
382 14 100       91 my $table_options = @table_options ? "\n".join("\n", @table_options) : '';
383             push @create, "CREATE TABLE $table_name_q (\n" .
384 64         249 join( ",\n", map { " $_" } @field_defs,
385 14 100       101 ($options->{delay_constraints} ? () : @constraint_defs) ) .
386             "\n)$table_options";
387              
388 14         64 @constraint_defs = map { "ALTER TABLE $table_name_q ADD $_" } @constraint_defs;
  13         59  
389              
390 14 50       69 if ( $WARN ) {
391 0 0       0 if ( %truncated ) {
392 0         0 warn "Truncated " . keys( %truncated ) . " names:\n";
393 0         0 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
394             }
395             }
396              
397 14 100       158 return \@create, \@fk_defs, \@trigger_defs, \@index_defs, ($options->{delay_constraints} ? \@constraint_defs : []);
398             }
399              
400             sub alter_field {
401 4     4 1 59 my ($from_field, $to_field, $options) = @_;
402              
403 4         14 my $qt = $options->{quote_table_names};
404 4         16 my ($field_create, $field_defs, $trigger_defs, $field_comments) =
405             create_field($to_field, $options, {});
406              
407             # Fix ORA-01442
408 4 100 100     80 if (!$from_field->is_nullable && $to_field->is_nullable) {
    100 66        
409 1 50       32 if ($from_field->data_type =~ /text/) {
410 0         0 die 'Cannot alter CLOB field in this way';
411             }
412             else {
413 1         3 @$field_defs = map { $_ .= ' NULL' } @$field_defs;
  1         6  
414             }
415             } elsif (!$from_field->is_nullable && !$to_field->is_nullable) {
416 1         28 @$field_defs = map { s/ NOT NULL//; $_} @$field_defs;
  1         6  
  1         5  
417             }
418              
419 4         129 my $table_name = quote($to_field->table->name,$qt);
420              
421 4         37 return 'ALTER TABLE '.$table_name.' MODIFY ( '.join('', @$field_defs).' )';
422             }
423              
424             sub drop_field
425             {
426 2     2 1 30 my ($old_field, $options) = @_;
427 2         18 my $qi = $options->{quote_identifiers};
428 2         47 my $table_name = quote($old_field->table->name, $qi);
429              
430 2         43 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
431             $table_name,
432             quote($old_field->name, $qi));
433              
434 2         10 return $out;
435             }
436              
437             sub add_field {
438 3     3 1 55 my ($new_field, $options) = @_;
439              
440 3         9 my $qt = $options->{quote_table_names};
441 3         18 my ($field_create, $field_defs, $trigger_defs, $field_comments) =
442             create_field($new_field, $options, {});
443              
444 3         67 my $table_name = quote($new_field->table->name,$qt);
445              
446 3         31 my $out = sprintf('ALTER TABLE %s ADD ( %s )',
447             $table_name,
448             join('', @$field_defs));
449 3         20 return $out;
450             }
451              
452             sub create_field {
453 60     60 1 149 my ($field, $options, $field_name_scope) = @_;
454 60         153 my $qf = $options->{quote_field_names};
455 60         137 my $qt = $options->{quote_table_names};
456              
457 60         113 my (@create, @field_defs, @trigger_defs, @field_comments);
458              
459 60         1337 my $table_name = $field->table->name;
460 60         1203 my $table_name_q = quote($table_name, $qt);
461              
462             #
463             # Field name
464             #
465 60         1215 my $field_name = mk_name(
466             $field->name, '', $field_name_scope, 1
467             );
468 60         173 my $field_name_q = quote($field_name, $qf);
469 60         151 my $field_def = quote($field_name, $qf);
470 60         1355 $field->name( $field_name );
471              
472             #
473             # Datatype
474             #
475 60         167 my $check;
476 60         245 my $data_type = lc $field->data_type;
477 60         1144 my @size = $field->size;
478 60         1668 my %extra = $field->extra;
479 60   100     323 my $list = $extra{'list'} || [];
480 60         205 my $commalist = join( ', ', map { __PACKAGE__->_quote_string($_) } @$list );
  2         6  
481              
482 60 100       242 if ( $data_type eq 'enum' ) {
    50          
483 1         7 $check = "CHECK ($field_name_q IN ($commalist))";
484 1         2 $data_type = 'varchar2';
485             }
486             elsif ( $data_type eq 'set' ) {
487             # XXX add a CHECK constraint maybe
488             # (trickier and slower, than enum :)
489 0         0 $data_type = 'varchar2';
490             }
491             else {
492 59 100       225 if (defined $translate{ $data_type }) {
493 53 50       173 if (ref $translate{ $data_type } eq "ARRAY") {
494 0         0 ($data_type,$size[0]) = @{$translate{ $data_type }};
  0         0  
495             } else {
496 53         139 $data_type = $translate{ $data_type };
497             }
498             }
499 59   50     165 $data_type ||= 'varchar2';
500             }
501              
502             # ensure size is not bigger than max size oracle allows for data type
503 60 100       238 if ( defined $max_size{$data_type} ) {
504 51         180 for ( my $i = 0 ; $i < scalar @size ; $i++ ) {
505             my $max =
506             ref( $max_size{$data_type} ) eq 'ARRAY'
507             ? $max_size{$data_type}->[$i]
508 55 100       204 : $max_size{$data_type};
509 55 50       313 $size[$i] = $max if $size[$i] > $max;
510             }
511             }
512              
513             #
514             # Fixes ORA-02329: column of datatype LOB cannot be
515             # unique or a primary key
516             #
517 60 100 100     597 if ( $data_type eq 'clob' && $field->is_primary_key ) {
518 1         11 $data_type = 'varchar2';
519 1         6 $size[0] = 4000;
520 1 50       5 warn "CLOB cannot be a primary key, changing to VARCHAR2\n"
521             if $WARN;
522             }
523              
524 60 50 66     401 if ( $data_type eq 'clob' && $field->is_unique ) {
525 0         0 $data_type = 'varchar2';
526 0         0 $size[0] = 4000;
527 0 0       0 warn "CLOB cannot be a unique key, changing to VARCHAR2\n"
528             if $WARN;
529             }
530              
531             #
532             # Fixes ORA-00907: missing right parenthesis
533             #
534 60 100       377 if ( $data_type =~ /(date|clob)/i ) {
535 8         28 undef @size;
536             }
537              
538             #
539             # Fixes ORA-00906: missing right parenthesis
540             # if size is 0 or undefined
541             #
542 60         147 for (qw/varchar2/) {
543 60 100       552 if ( $data_type =~ /^($_)$/i ) {
544 26   66     116 $size[0] ||= $max_size{$_};
545             }
546             }
547              
548 60         189 $field_def .= " $data_type";
549 60 100 100     311 if ( defined $size[0] && $size[0] > 0 ) {
550 49         184 $field_def .= '(' . join( ',', @size ) . ')';
551             }
552              
553             #
554             # Default value
555             #
556 60         219 my $default = $field->default_value;
557 60 100       170 if ( defined $default ) {
558 16         95 debug("ORA: Handling default value: $default");
559             #
560             # Wherein we try to catch a string being used as
561             # a default value for a numerical field. If "true/false,"
562             # then sub "1/0," otherwise just test the truthity of the
563             # argument and use that (naive?).
564             #
565 16 100 66     255 if (ref $default and defined $$default) {
    50 100        
    100 66        
    50 0        
      33        
566 1         4 $default = $$default;
567             } elsif (ref $default) {
568 0         0 $default = 'NULL';
569             } elsif (
570             $data_type =~ /^number$/i &&
571             $default !~ /^-?\d+$/ &&
572             $default !~ m/null/i
573             ) {
574 1 50       10 if ( $default =~ /^true$/i ) {
    50          
575 0         0 $default = "'1'";
576             } elsif ( $default =~ /^false$/i ) {
577 0         0 $default = "'0'";
578             } else {
579 1 50       5 $default = $default ? "'1'" : "'0'";
580             }
581             } elsif (
582             $data_type =~ /date/ && (
583             $default eq 'current_timestamp'
584             ||
585             $default eq 'now()'
586             )
587             ) {
588 0         0 $default = 'SYSDATE';
589             } else {
590 14 50       118 $default = $default =~ m/null/i ? 'NULL' : __PACKAGE__->_quote_string($default);
591             }
592              
593 16         54 $field_def .= " DEFAULT $default",
594             }
595              
596             #
597             # Not null constraint
598             #
599 60 100       1479 unless ( $field->is_nullable ) {
600 35         2079 debug("ORA: Field is NOT NULL");
601 35         108 $field_def .= ' NOT NULL';
602             }
603              
604 60 100       1389 $field_def .= " $check" if $check;
605              
606             #
607             # Auto_increment
608             #
609 60 100       1111 if ( $field->is_auto_increment ) {
610 7         87 debug("ORA: Handling auto increment");
611 7         41 my $base_name = $table_name . "_". $field_name;
612 7         31 my $seq_name = quote(mk_name( $base_name, 'sq' ),$qt);
613 7         31 my $trigger_name = quote(mk_name( $base_name, 'ai' ),$qt);
614              
615 7 100       43 push @create, qq[DROP SEQUENCE $seq_name] if $options->{add_drop_table};
616 7         32 push @create, "CREATE SEQUENCE $seq_name";
617 7         99 my $trigger =
618             "CREATE OR REPLACE TRIGGER $trigger_name\n" .
619             "BEFORE INSERT ON $table_name_q\n" .
620             "FOR EACH ROW WHEN (\n" .
621             " new.$field_name_q IS NULL".
622             " OR new.$field_name_q = 0\n".
623             ")\n".
624             "BEGIN\n" .
625             " SELECT $seq_name.nextval\n" .
626             " INTO :new." . $field_name_q."\n" .
627             " FROM dual;\n" .
628             "END;\n";
629              
630 7         32 push @trigger_defs, $trigger;
631             }
632              
633 60         691 push @field_defs, $field_def;
634              
635 60 100       1194 if ( my $comment = $field->comments ) {
636 1         14 debug("ORA: Handling comment");
637 1         5 $comment =~ __PACKAGE__->_quote_string($comment);
638             push @field_comments,
639             "COMMENT ON COLUMN $table_name_q.$field_name_q is\n $comment;"
640 1 50       7 unless $options->{no_comments};
641             }
642              
643 60         898 return \@create, \@field_defs, \@trigger_defs, \@field_comments;
644              
645             }
646              
647             sub drop_table {
648 2     2 0 25 my ($table, $options) = @_;
649              
650 2         8 my $qi = $options->{quote_identifiers};
651 2         7 my @foreign_key_constraints = grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints;
  3         111  
652 2         41 my @statements;
653 2         6 for my $constraint(@foreign_key_constraints) {
654 1         4 push @statements, alter_drop_constraint($constraint, $options);
655             }
656              
657 2         8 return @statements, 'DROP TABLE ' . quote($table, $qi);
658             }
659              
660             sub alter_create_index {
661 1     1 0 12 my ($index, $options) = @_;
662 1         6 return create_index($index, $options);
663             }
664              
665             sub create_index {
666 8     8 1 30 my ( $index, $options, $index_options) = @_;
667 8   100     164 $index_options = $index_options || '';
668 8   66     62 my $qf = $options->{quote_field_names} || $options->{quote_identifiers};
669 8   66     49 my $qt = $options->{quote_table_names} || $options->{quote_identifiers};
670 8   50     174 my $index_name = $index->name || '';
671 8 50 0     129 $index_name = $index_name ? mk_name( $index_name ) : mk_name( $index->table, $index_name || 'i' );
672             return join(
673             ' ',
674 48 50       318 map { $_ || () }
675             'CREATE',
676             lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
677             $index_name ? quote($index_name, $qf): '',
678             'ON',
679             quote($index->table, $qt),
680 8 100       185 '(' . join( ', ', map { quote($_, $qf) } $index->fields ) . ")$index_options"
  9 50       37  
681             );
682             }
683              
684             sub alter_drop_index {
685 1     1 0 10 my ($index, $options) = @_;
686 1         17 return 'DROP INDEX ' . $index->name;
687             }
688              
689             sub alter_drop_constraint {
690 9     9 0 74 my ($c, $options) = @_;
691 9         18 my $qi = $options->{quote_identifiers};
692 9         165 my $table_name = quote($c->table->name, $qi);
693 9         27 my @out = ('ALTER','TABLE',$table_name,'DROP',);
694 9 100       173 if ($c->name) {
    50          
695 8         148 push @out, ('CONSTRAINT',quote($c->name, $qi));
696             }
697             elsif ($c->type eq PRIMARY_KEY) {
698 1         21 push @out, 'PRIMARY KEY';
699             }
700 9         50 return join(' ',@out);
701             }
702              
703             sub alter_create_constraint {
704 5     5 0 50 my ($c, $options) = @_;
705 5         14 my $qi = $options->{quote_identifiers};
706 5         94 my $table_name = quote($c->table->name, $qi);
707 5         22 return join( ' ',
708             'ALTER TABLE',
709             $table_name,
710             'ADD',
711             create_constraint($c, $options) );
712             }
713              
714             sub create_constraint {
715 21     21 1 62 my ($c, $options) = @_;
716              
717 21         58 my $qt = $options->{quote_table_names};
718 21         53 my $qf = $options->{quote_field_names};
719 21         435 my $table = $c->table;
720 21         858 my $table_name = $table->name;
721 21         448 my $table_name_q = quote($table_name,$qt);
722 21   100     521 my $name = $c->name || '';
723 21         91 my @fields = map { quote($_,$qf) } $c->fields;
  22         94  
724 21         706 my @rfields = map { quote($_,$qf) } $c->reference_fields;
  7         28  
725              
726 21 50 33     98 return undef if !@fields && $c->type ne 'CHECK';
727              
728 21         50 my $definition;
729              
730 21 100       537 if ( $c->type eq PRIMARY_KEY ) {
    100          
    100          
    50          
731 12         330 debug("ORA: Creating PK constraint on fields (" . join(', ', @fields) . ")");
732             # create a name if delay_constraints
733             $name ||= mk_name( $table_name, 'pk' )
734 12 100 33     97 if $options->{delay_constraints};
735 12         44 $name = quote($name,$qf);
736 12 100       90 $definition = ($name ? "CONSTRAINT $name " : '') .
737             'PRIMARY KEY (' . join( ', ', @fields ) . ')';
738             }
739             elsif ( $c->type eq UNIQUE ) {
740             # Don't create UNIQUE constraints identical to the primary key
741 2 50       65 if ( my $pk = $table->primary_key ) {
742 2         78 my $u_fields = join(":", @fields);
743 2         10 my $pk_fields = join(":", $pk->fields);
744 2 50       63 next if $u_fields eq $pk_fields;
745             }
746              
747 2 50       9 if ($name) {
748             # Force prepend of table_name as ORACLE doesn't allow duplicate
749             # CONSTRAINT names even for different tables (ORA-02264)
750 2 50       34 $name = mk_name( "${table_name}_$name", 'u' ) unless $name =~ /^$table_name/;
751             }
752             else {
753 0         0 $name = mk_name( $table_name, 'u' );
754             }
755 2         24 debug("ORA: Creating UNIQUE constraint on fields (" . join(', ', @fields) . ") named $name");
756 2         13 $name = quote($name, $qf);
757              
758 2         14 for my $f ( $c->fields ) {
759 2 50       12 my $field_def = $table->get_field( $f ) or next;
760 2 50       67 my $dtype = $translate{ ref $field_def->data_type eq "ARRAY" ? $field_def->data_type->[0] : $field_def->data_type} or next;
    50          
761 2 50 33     17 if ( $WARN && $dtype =~ /clob/i ) {
762 0         0 warn "Oracle will not allow UNIQUE constraints on " .
763             "CLOB field '" . $field_def->table->name . '.' .
764             $field_def->name . ".'\n"
765             }
766             }
767              
768 2         21 $definition = "CONSTRAINT $name UNIQUE " .
769             '(' . join( ', ', @fields ) . ')';
770             }
771             elsif ( $c->type eq CHECK_C ) {
772 1   0     22 $name ||= mk_name( $name || $table_name, 'ck' );
      33        
773 1         4 $name = quote($name, $qf);
774 1   50     20 my $expression = $c->expression || '';
775 1         6 debug("ORA: Creating CHECK constraint on fields (" . join(', ', @fields) . ") named $name");
776 1         5 $definition = "CONSTRAINT $name CHECK ($expression)";
777             }
778             elsif ( $c->type eq FOREIGN_KEY ) {
779 6         151 $name = mk_name( join('_', $table_name, $c->fields). '_fk' );
780 6         23 $name = quote($name, $qf);
781 6   100     195 my $on_delete = uc ($c->on_delete || '');
782              
783 6         32 $definition = "CONSTRAINT $name FOREIGN KEY ";
784              
785 6 50       24 if ( @fields ) {
786 6         31 $definition .= '(' . join( ', ', @fields ) . ')';
787             }
788              
789 6         130 my $ref_table = quote($c->reference_table,$qt);
790 6         47 debug("ORA: Creating FK constraint on fields (" . join(', ', @fields) . ") named $name referencing $ref_table");
791 6         26 $definition .= " REFERENCES $ref_table";
792              
793 6 50       22 if ( @rfields ) {
794 6         35 $definition .= ' (' . join( ', ', @rfields ) . ')';
795             }
796              
797 6 50       144 if ( $c->match_type ) {
798 0 0       0 $definition .= ' MATCH ' .
799             ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
800             }
801              
802 6 100 66     171 if ( $on_delete && $on_delete ne "RESTRICT") {
803 1         23 $definition .= ' ON DELETE '.$c->on_delete;
804             }
805             }
806              
807 21 50       233 return $definition ? $definition : undef;
808             }
809              
810             sub create_view {
811 2     2 1 53 my ($view, $options) = @_;
812 2         9 my $qt = $options->{quote_table_names};
813 2         25 my $view_name = quote($view->name,$qt);
814 2         53 my $extra = $view->extra;
815              
816 2         9 my $view_type = 'VIEW';
817 2         10 my $view_options = '';
818 2 100       13 if ( my $materialized = $extra->{materialized} ) {
819 1         3 $view_type = 'MATERIALIZED VIEW';
820 1         13 $view_options .= ' '.$materialized;
821             }
822              
823 2         5 my @create;
824             push @create, qq[DROP $view_type $view_name]
825 2 100       12 if $options->{add_drop_view};
826              
827 2         26 push @create, sprintf("CREATE %s %s%s AS\n%s",
828             $view_type,
829             $view_name,
830             $view_options,
831             $view->sql);
832              
833 2         13 return \@create;
834             }
835              
836             sub mk_name {
837 92   50 92 0 1572 my $basename = shift || '';
838 92   100     398 my $type = shift || '';
839 92 50       292 $type = '' if $type =~ /^\d/;
840 92   100     308 my $scope = shift || '';
841 92   100     285 my $critical = shift || '';
842 92         181 my $basename_orig = $basename;
843 92 100       253 my $max_name = $type
844             ? $max_id_length - (length($type) + 1)
845             : $max_id_length;
846 92 100       261 $basename = substr( $basename, 0, $max_name )
847             if length( $basename ) > $max_name;
848 92 100       242 my $name = $type ? "${type}_$basename" : $basename;
849              
850 92 50 66     323 if ( $basename ne $basename_orig and $critical ) {
851 0 0       0 my $show_type = $type ? "+'$type'" : "";
852 0 0       0 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
853             "character limit to make '$name'\n" if $WARN;
854 0         0 $truncated{ $basename_orig } = $name;
855             }
856              
857 92   100     358 $scope ||= \%global_names;
858 92 100       323 if ( my $prev = $scope->{ $name } ) {
859 2         6 my $name_orig = $name;
860 2 50       11 substr($name, $max_id_length - 2) = ""
861             if length( $name ) >= $max_id_length - 1;
862 2         16 $name .= sprintf( "%02d", $prev++ );
863              
864 2 50       8 warn "The name '$name_orig' has been changed to ",
865             "'$name' to make it unique.\n" if $WARN;
866              
867 2         5 $scope->{ $name_orig }++;
868             }
869              
870 92         269 $scope->{ $name }++;
871 92         263 return $name;
872             }
873              
874             1;
875              
876             sub quote {
877 355     355 0 1635 my ($name, $q) = @_;
878 355 100 100     1579 return $name unless $q && $name;
879 166         862 $name =~ s/\Q$quote_char/$quote_char$quote_char/g;
880 166         1091 return "$quote_char$name$quote_char";
881             }
882              
883              
884             # -------------------------------------------------------------------
885             # All bad art is the result of good intentions.
886             # Oscar Wilde
887             # -------------------------------------------------------------------
888              
889             =pod
890              
891             =head1 CREDITS
892              
893             Mad props to Tim Bunce for much of the logic stolen from his "mysql2ora"
894             script.
895              
896             =head1 AUTHORS
897              
898             Ken Youens-Clark Ekclark@cpan.orgE,
899             Alexander Hartmaier Eabraxxa@cpan.orgE,
900             Fabien Wernli Efaxmodem@cpan.orgE.
901              
902             =head1 SEE ALSO
903              
904             SQL::Translator, DDL::Oracle, mysql2ora.
905              
906             =cut