diff --git a/lib/SQL/Translator/Parser/Oracle.pm b/lib/SQL/Translator/Parser/Oracle.pm index 66a885c75..388452b71 100644 --- a/lib/SQL/Translator/Parser/Oracle.pm +++ b/lib/SQL/Translator/Parser/Oracle.pm @@ -489,7 +489,17 @@ parens_name_list : '(' NAME(s /,/) ')' field_meta : default_val | column_constraint -default_val : /default/i VALUE +default_val : + /default/i CURRENT_TIMESTAMP + { + my $val = $item[2]; + $return = { + supertype => 'constraint', + type => 'default', + value => $val, + } + } + | /default/i VALUE { my $val = $item[2]; $return = { @@ -616,6 +626,11 @@ VALUE : /[-+]?\d*\.?\d+(?:[eE]\d+)?/ | /null/i { 'NULL' } +# always a scalar-ref, so that it is treated as a function and not quoted by consumers +CURRENT_TIMESTAMP : + /current_timestamp(\(\))?/i { \'CURRENT_TIMESTAMP' } + | /now\(\)/i { \'CURRENT_TIMESTAMP' } + END_OF_GRAMMAR sub parse { diff --git a/lib/SQL/Translator/Producer/Oracle.pm b/lib/SQL/Translator/Producer/Oracle.pm index 017f41a26..dae39e5f3 100644 --- a/lib/SQL/Translator/Producer/Oracle.pm +++ b/lib/SQL/Translator/Producer/Oracle.pm @@ -95,7 +95,8 @@ $DEBUG = 0 unless defined $DEBUG; use base 'SQL::Translator::Producer'; use SQL::Translator::Schema::Constants; -use SQL::Translator::Utils qw(header_comment); +use SQL::Translator::Utils qw(debug header_comment); +use Data::Dumper; my %translate = ( # @@ -196,6 +197,7 @@ sub produce { my $delay_constraints = $translator->producer_args->{delay_constraints}; my ($output, $create, @table_defs, @fk_defs, @trigger_defs, @index_defs, @constraint_defs); + debug("ORA: Beginning production"); $create .= header_comment unless ($no_comments); my $qt = 1 if $translator->quote_table_names; my $qf = 1 if $translator->quote_field_names; @@ -211,6 +213,7 @@ sub produce { } for my $table ( $schema->get_tables ) { + debug("ORA: Producing for table " . $table->name); my ( $table_def, $fk_def, $trigger_def, $index_def, $constraint_def ) = create_table( $table, { @@ -270,213 +273,119 @@ sub create_table { push @create, "--\n-- Table: $table_name\n--" unless $options->{no_comments}; push @create, qq[DROP TABLE $table_name_q CASCADE CONSTRAINTS] if $options->{add_drop_table}; - my ( %field_name_scope, @field_comments ); - for my $field ( $table->get_fields ) { - my ($field_create, $field_defs, $trigger_defs, $field_comments) = - create_field($field, $options, \%field_name_scope); - push @create, @$field_create if ref $field_create; - push @field_defs, @$field_defs if ref $field_defs; - push @trigger_defs, @$trigger_defs if ref $trigger_defs; - push @field_comments, @$field_comments if ref $field_comments; + my ( %field_name_scope, @field_comments ); + for my $field ( $table->get_fields ) { + debug("ORA: Creating field " . $field->name . "(" . $field->data_type . ")"); + my ($field_create, $field_defs, $trigger_defs, $field_comments) = + create_field($field, $options, \%field_name_scope); + push @create, @$field_create if ref $field_create; + push @field_defs, @$field_defs if ref $field_defs; + push @trigger_defs, @$trigger_defs if ref $trigger_defs; + push @field_comments, @$field_comments if ref $field_comments; + } + + # + # Table options + # + my @table_options; + for my $opt ( $table->options ) { + if ( ref $opt eq 'HASH' ) { + my ( $key, $value ) = each %$opt; + if ( ref $value eq 'ARRAY' ) { + push @table_options, "$key\n(\n". join ("\n", + map { " $_->[0]\t$_->[1]" } + map { [ each %$_ ] } + @$value + )."\n)"; + } + elsif ( !defined $value ) { + push @table_options, $key; + } + else { + push @table_options, "$key $value"; + } } + } - # - # Table options - # - my @table_options; - for my $opt ( $table->options ) { + # + # Table constraints + # + for my $c ( $table->get_constraints ) { + my $constr = create_constraint($c, $options); + if ($constr) { + if ($c->type eq FOREIGN_KEY) { # FK defs always come later as alters + push @fk_defs, sprintf("ALTER TABLE %s ADD %s", $table_name_q, $constr); + } + else { + push @constraint_defs, $constr; + } + } + } + + # + # Index Declarations + # + my @index_defs = (); + for my $index ( $table->get_indices ) { + my $index_name = $index->name || ''; + my $index_type = $index->type || NORMAL; + my @fields = map { quote($_, $qf) } $index->fields; + next unless @fields; + debug("ORA: Creating $index_type index on fields (" . join(', ', @fields) . ") named $index_name"); + my @index_options; + for my $opt ( $index->options ) { if ( ref $opt eq 'HASH' ) { my ( $key, $value ) = each %$opt; if ( ref $value eq 'ARRAY' ) { push @table_options, "$key\n(\n". join ("\n", map { " $_->[0]\t$_->[1]" } map { [ each %$_ ] } - @$value + @$value )."\n)"; } elsif ( !defined $value ) { - push @table_options, $key; + push @index_options, $key; } else { - push @table_options, "$key $value"; + push @index_options, "$key $value"; } } } - - # - # Table constraints - # - for my $c ( $table->get_constraints ) { - my $name = $c->name || ''; - my @fields = map { quote($_,$qf) } $c->fields; - my @rfields = map { quote($_,$qf) } $c->reference_fields; - - next if !@fields && $c->type ne CHECK_C; - - if ( $c->type eq PRIMARY_KEY ) { - # create a name if delay_constraints - $name ||= mk_name( $table_name, 'pk' ) - if $options->{delay_constraints}; - $name = quote($name,$qf); - push @constraint_defs, ($name ? "CONSTRAINT $name " : '') . - 'PRIMARY KEY (' . join( ', ', @fields ) . ')'; - } - elsif ( $c->type eq UNIQUE ) { - # Don't create UNIQUE constraints identical to the primary key - if ( my $pk = $table->primary_key ) { - my $u_fields = join(":", @fields); - my $pk_fields = join(":", $pk->fields); - next if $u_fields eq $pk_fields; - } - - if ($name) { - # Force prepend of table_name as ORACLE doesn't allow duplicate - # CONSTRAINT names even for different tables (ORA-02264) - $name = mk_name( "${table_name}_$name", 'u' ) unless $name =~ /^$table_name/; - } - else { - $name = mk_name( $table_name, 'u' ); - } - - $name = quote($name, $qf); - - for my $f ( $c->fields ) { - my $field_def = $table->get_field( $f ) or next; - my $dtype = $translate{ ref $field_def->data_type eq "ARRAY" ? $field_def->data_type->[0] : $field_def->data_type} or next; - if ( $WARN && $dtype =~ /clob/i ) { - warn "Oracle will not allow UNIQUE constraints on " . - "CLOB field '" . $field_def->table->name . '.' . - $field_def->name . ".'\n" - } - } - - push @constraint_defs, "CONSTRAINT $name UNIQUE " . - '(' . join( ', ', @fields ) . ')'; - } - elsif ( $c->type eq CHECK_C ) { - $name ||= mk_name( $name || $table_name, 'ck' ); - $name = quote($name, $qf); - my $expression = $c->expression || ''; - push @constraint_defs, "CONSTRAINT $name CHECK ($expression)"; - } - elsif ( $c->type eq FOREIGN_KEY ) { - $name = mk_name( join('_', $table_name, $c->fields). '_fk' ); - $name = quote($name, $qf); - my $on_delete = uc ($c->on_delete || ''); - - my $def = "CONSTRAINT $name FOREIGN KEY "; - - if ( @fields ) { - $def .= '(' . join( ', ', @fields ) . ')'; - } - - my $ref_table = quote($c->reference_table,$qt); - - $def .= " REFERENCES $ref_table"; - - if ( @rfields ) { - $def .= ' (' . join( ', ', @rfields ) . ')'; - } - - if ( $c->match_type ) { - $def .= ' MATCH ' . - ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL'; - } - - if ( $on_delete && $on_delete ne "RESTRICT") { - $def .= ' ON DELETE '.$c->on_delete; - } - - # disabled by plu 2007-12-29 - doesn't exist for oracle - #if ( $c->on_update ) { - # $def .= ' ON UPDATE '. $c->on_update; - #} - - push @fk_defs, sprintf("ALTER TABLE %s ADD %s", $table_name_q, $def); - } + my $index_options = @index_options + ? "\n".join("\n", @index_options) : ''; + + if ( $index_type eq PRIMARY_KEY ) { + $index_name = $index_name ? mk_name( $index_name ) + : mk_name( $table_name, 'pk' ); + $index_name = quote($index_name, $qf); + push @field_defs, 'CONSTRAINT '.$index_name.' PRIMARY KEY '. + '(' . join( ', ', @fields ) . ')'; } - - # - # Index Declarations - # - my @index_defs = (); - for my $index ( $table->get_indices ) { - my $index_name = $index->name || ''; - my $index_type = $index->type || NORMAL; - my @fields = map { quote($_, $qf) } $index->fields; - next unless @fields; - - my @index_options; - for my $opt ( $index->options ) { - if ( ref $opt eq 'HASH' ) { - my ( $key, $value ) = each %$opt; - if ( ref $value eq 'ARRAY' ) { - push @table_options, "$key\n(\n". join ("\n", - map { " $_->[0]\t$_->[1]" } - map { [ each %$_ ] } - @$value - )."\n)"; - } - elsif ( !defined $value ) { - push @index_options, $key; - } - else { - push @index_options, "$key $value"; - } - } - } - my $index_options = @index_options - ? "\n".join("\n", @index_options) : ''; - - if ( $index_type eq PRIMARY_KEY ) { - $index_name = $index_name ? mk_name( $index_name ) - : mk_name( $table_name, 'pk' ); - $index_name = quote($index_name, $qf); - push @field_defs, 'CONSTRAINT '.$index_name.' PRIMARY KEY '. - '(' . join( ', ', @fields ) . ')'; - } - elsif ( $index_type eq NORMAL ) { - $index_name = $index_name ? mk_name( $index_name ) - : mk_name( $table_name, $index_name || 'i' ); - $index_name = quote($index_name, $qf); - push @index_defs, - "CREATE INDEX $index_name on $table_name_q (". - join( ', ', @fields ). - ")$index_options"; - } - elsif ( $index_type eq UNIQUE ) { - $index_name = $index_name ? mk_name( $index_name ) - : mk_name( $table_name, $index_name || 'i' ); - $index_name = quote($index_name, $qf); - push @index_defs, - "CREATE UNIQUE INDEX $index_name on $table_name_q (". - join( ', ', @fields ). - ")$index_options"; - } - else { - warn "Unknown index type ($index_type) on table $table_name.\n" - if $WARN; - } + elsif ($index_type eq NORMAL or $index_type eq UNIQUE) { + push @index_defs, create_index($index, $options, $index_options); } + else { + warn "Unknown index type ($index_type) on table $table_name.\n" + if $WARN; + } + } - if ( my @table_comments = $table->comments ) { - for my $comment ( @table_comments ) { - next unless $comment; - $comment = __PACKAGE__->_quote_string($comment); - push @field_comments, "COMMENT ON TABLE $table_name_q is\n $comment" - unless $options->{no_comments}; - } + if ( my @table_comments = $table->comments ) { + for my $comment ( @table_comments ) { + next unless $comment; + $comment = __PACKAGE__->_quote_string($comment); + push @field_comments, "COMMENT ON TABLE $table_name_q is\n $comment" + unless $options->{no_comments}; } + } - my $table_options = @table_options - ? "\n".join("\n", @table_options) : ''; + my $table_options = @table_options ? "\n".join("\n", @table_options) : ''; push @create, "CREATE TABLE $table_name_q (\n" . join( ",\n", map { " $_" } @field_defs, ($options->{delay_constraints} ? () : @constraint_defs) ) . "\n)$table_options"; - @constraint_defs = map { "ALTER TABLE $table_name_q ADD $_" } - @constraint_defs; + @constraint_defs = map { "ALTER TABLE $table_name_q ADD $_" } @constraint_defs; if ( $WARN ) { if ( %truncated ) { @@ -496,8 +405,13 @@ sub alter_field { create_field($to_field, $options, {}); # Fix ORA-01442 - if ($to_field->is_nullable && !$from_field->is_nullable) { - die 'Cannot remove NOT NULL from table field'; + if (!$from_field->is_nullable && $to_field->is_nullable) { + if ($from_field->data_type =~ /text/) { + die 'Cannot alter CLOB field in this way'; + } + else { + @$field_defs = map { $_ .= ' NULL' } @$field_defs; + } } elsif (!$from_field->is_nullable && !$to_field->is_nullable) { @$field_defs = map { s/ NOT NULL//; $_} @$field_defs; } @@ -507,6 +421,19 @@ sub alter_field { return 'ALTER TABLE '.$table_name.' MODIFY ( '.join('', @$field_defs).' )'; } +sub drop_field +{ + my ($old_field, $options) = @_; + my $qi = $options->{quote_identifiers}; + my $table_name = quote($old_field->table->name, $qi); + + my $out = sprintf('ALTER TABLE %s DROP COLUMN %s', + $table_name, + quote($old_field->name, $qi)); + + return $out; +} + sub add_field { my ($new_field, $options) = @_; @@ -628,6 +555,7 @@ sub create_field { # my $default = $field->default_value; if ( defined $default ) { + debug("ORA: Handling default value: $default"); # # Wherein we try to catch a string being used as # a default value for a numerical field. If "true/false," @@ -669,6 +597,7 @@ sub create_field { # Not null constraint # unless ( $field->is_nullable ) { + debug("ORA: Field is NOT NULL"); $field_def .= ' NOT NULL'; } @@ -678,6 +607,7 @@ sub create_field { # Auto_increment # if ( $field->is_auto_increment ) { + debug("ORA: Handling auto increment"); my $base_name = $table_name . "_". $field_name; my $seq_name = quote(mk_name( $base_name, 'sq' ),$qt); my $trigger_name = quote(mk_name( $base_name, 'ai' ),$qt); @@ -700,23 +630,10 @@ sub create_field { push @trigger_defs, $trigger; } - if ( lc $field->data_type eq 'timestamp' ) { - my $base_name = $table_name . "_". $field_name; - my $trig_name = quote(mk_name( $base_name, 'ts' ), $qt); - my $trigger = - "CREATE OR REPLACE TRIGGER $trig_name\n". - "BEFORE INSERT OR UPDATE ON $table_name_q\n". - "FOR EACH ROW WHEN (new.$field_name_q IS NULL)\n". - "BEGIN\n". - " SELECT sysdate INTO :new.$field_name_q FROM dual;\n". - "END;\n"; - - push @trigger_defs, $trigger; - } - push @field_defs, $field_def; if ( my $comment = $field->comments ) { + debug("ORA: Handling comment"); $comment =~ __PACKAGE__->_quote_string($comment); push @field_comments, "COMMENT ON COLUMN $table_name_q.$field_name_q is\n $comment;" @@ -727,6 +644,168 @@ sub create_field { } +sub drop_table { + my ($table, $options) = @_; + + my $qi = $options->{quote_identifiers}; + my @foreign_key_constraints = grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints; + my @statements; + for my $constraint(@foreign_key_constraints) { + push @statements, alter_drop_constraint($constraint, $options); + } + + return @statements, 'DROP TABLE ' . quote($table, $qi); +} + +sub alter_create_index { + my ($index, $options) = @_; + return create_index($index, $options); +} + +sub create_index { + my ( $index, $options, $index_options) = @_; + $index_options = $index_options || ''; + my $qf = $options->{quote_field_names} || $options->{quote_identifiers}; + my $qt = $options->{quote_table_names} || $options->{quote_identifiers}; + my $index_name = $index->name || ''; + $index_name = $index_name ? mk_name( $index_name ) : mk_name( $index->table, $index_name || 'i' ); + return join( + ' ', + map { $_ || () } + 'CREATE', + lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX', + $index_name ? quote($index_name, $qf): '', + 'ON', + quote($index->table, $qt), + '(' . join( ', ', map { quote($_, $qf) } $index->fields ) . ")$index_options" + ); +} + +sub alter_drop_index { + my ($index, $options) = @_; + return 'DROP INDEX ' . $index->name; +} + +sub alter_drop_constraint { + my ($c, $options) = @_; + my $qi = $options->{quote_identifiers}; + my $table_name = quote($c->table->name, $qi); + my @out = ('ALTER','TABLE',$table_name,'DROP',); + if ($c->name) { + push @out, ('CONSTRAINT',quote($c->name, $qi)); + } + elsif ($c->type eq PRIMARY_KEY) { + push @out, 'PRIMARY KEY'; + } + return join(' ',@out); +} + +sub alter_create_constraint { + my ($c, $options) = @_; + my $qi = $options->{quote_identifiers}; + my $table_name = quote($c->table->name, $qi); + return join( ' ', + 'ALTER TABLE', + $table_name, + 'ADD', + create_constraint($c, $options) ); +} + +sub create_constraint { + my ($c, $options) = @_; + + my $qt = $options->{quote_table_names}; + my $qf = $options->{quote_field_names}; + my $table = $c->table; + my $table_name = $table->name; + my $table_name_q = quote($table_name,$qt); + my $name = $c->name || ''; + my @fields = map { quote($_,$qf) } $c->fields; + my @rfields = map { quote($_,$qf) } $c->reference_fields; + + return undef if !@fields && $c->type ne 'CHECK'; + + my $definition; + + if ( $c->type eq PRIMARY_KEY ) { + debug("ORA: Creating PK constraint on fields (" . join(', ', @fields) . ")"); + # create a name if delay_constraints + $name ||= mk_name( $table_name, 'pk' ) + if $options->{delay_constraints}; + $name = quote($name,$qf); + $definition = ($name ? "CONSTRAINT $name " : '') . + 'PRIMARY KEY (' . join( ', ', @fields ) . ')'; + } + elsif ( $c->type eq UNIQUE ) { + # Don't create UNIQUE constraints identical to the primary key + if ( my $pk = $table->primary_key ) { + my $u_fields = join(":", @fields); + my $pk_fields = join(":", $pk->fields); + next if $u_fields eq $pk_fields; + } + + if ($name) { + # Force prepend of table_name as ORACLE doesn't allow duplicate + # CONSTRAINT names even for different tables (ORA-02264) + $name = mk_name( "${table_name}_$name", 'u' ) unless $name =~ /^$table_name/; + } + else { + $name = mk_name( $table_name, 'u' ); + } + debug("ORA: Creating UNIQUE constraint on fields (" . join(', ', @fields) . ") named $name"); + $name = quote($name, $qf); + + for my $f ( $c->fields ) { + my $field_def = $table->get_field( $f ) or next; + my $dtype = $translate{ ref $field_def->data_type eq "ARRAY" ? $field_def->data_type->[0] : $field_def->data_type} or next; + if ( $WARN && $dtype =~ /clob/i ) { + warn "Oracle will not allow UNIQUE constraints on " . + "CLOB field '" . $field_def->table->name . '.' . + $field_def->name . ".'\n" + } + } + + $definition = "CONSTRAINT $name UNIQUE " . + '(' . join( ', ', @fields ) . ')'; + } + elsif ( $c->type eq CHECK_C ) { + $name ||= mk_name( $name || $table_name, 'ck' ); + $name = quote($name, $qf); + my $expression = $c->expression || ''; + debug("ORA: Creating CHECK constraint on fields (" . join(', ', @fields) . ") named $name"); + $definition = "CONSTRAINT $name CHECK ($expression)"; + } + elsif ( $c->type eq FOREIGN_KEY ) { + $name = mk_name( join('_', $table_name, $c->fields). '_fk' ); + $name = quote($name, $qf); + my $on_delete = uc ($c->on_delete || ''); + + $definition = "CONSTRAINT $name FOREIGN KEY "; + + if ( @fields ) { + $definition .= '(' . join( ', ', @fields ) . ')'; + } + + my $ref_table = quote($c->reference_table,$qt); + debug("ORA: Creating FK constraint on fields (" . join(', ', @fields) . ") named $name referencing $ref_table"); + $definition .= " REFERENCES $ref_table"; + + if ( @rfields ) { + $definition .= ' (' . join( ', ', @rfields ) . ')'; + } + + if ( $c->match_type ) { + $definition .= ' MATCH ' . + ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL'; + } + + if ( $on_delete && $on_delete ne "RESTRICT") { + $definition .= ' ON DELETE '.$c->on_delete; + } + } + + return $definition ? $definition : undef; +} sub create_view { my ($view, $options) = @_; diff --git a/t/15oracle-parser.t b/t/15oracle-parser.t index 8cae44a73..ee610fb32 100644 --- a/t/15oracle-parser.t +++ b/t/15oracle-parser.t @@ -7,7 +7,7 @@ use SQL::Translator; use SQL::Translator::Schema::Constants; use Test::SQL::Translator qw(maybe_plan); -maybe_plan(99, 'SQL::Translator::Parser::Oracle'); +maybe_plan(103, 'SQL::Translator::Parser::Oracle'); SQL::Translator::Parser::Oracle->import('parse'); my $t = SQL::Translator->new( trace => 0 ); @@ -34,6 +34,7 @@ my $sql = q[ trait_symbol VARCHAR2(100 BYTE) NOT NULL, trait_name VARCHAR2(200 CHAR) NOT NULL, qtl_trait_category_id NUMBER(11) NOT NULL, + trait_date DATE DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE ( trait_symbol ), UNIQUE ( trait_name ), FOREIGN KEY ( qtl_trait_category_id ) REFERENCES qtl_trait_category @@ -179,7 +180,7 @@ is( $t2->name, 'qtl_trait', 'Table "qtl_trait" exists' ); is( $t2->comments, 'foo bar comment', 'Comment "foo bar" exists' ); my @t2_fields = $t2->get_fields; -is( scalar @t2_fields, 4, '4 fields in table' ); +is( scalar @t2_fields, 5, '5 fields in table' ); my $t2_f1 = shift @t2_fields; is( $t2_f1->name, 'qtl_trait_id', 'First field is "qtl_trait_id"' ); @@ -217,6 +218,16 @@ is( $f4_fk->reference_table, 'qtl_trait_category', is( join(',', $f4_fk->reference_fields), 'qtl_trait_category_id', 'FK references field "qtl_trait_category_id"' ); +my $t2_f5 = shift @t2_fields; +is( $t2_f5->name, 'trait_date', 'Fifth field is "trait_date"'); +is( $t2_f5->data_type, 'DATE', 'Field is a timestamp' ); +is( $t2_f5->is_nullable, 0, 'Field cannot be null' ); +is_deeply( + $t2_f5->default_value, + \'CURRENT_TIMESTAMP', + 'Field has correct default value' +); + my @t2_constraints = $t2->get_constraints; is( scalar @t2_constraints, 4, '4 constraints on table' ); diff --git a/t/51-xml-to-oracle.t b/t/51-xml-to-oracle.t index 2486246c4..d6049c1d3 100644 --- a/t/51-xml-to-oracle.t +++ b/t/51-xml-to-oracle.t @@ -58,19 +58,19 @@ my $want = [ CONSTRAINT u_Basic_emailuniqueindex UNIQUE (email), CONSTRAINT u_Basic_very_long_index_name_o UNIQUE (title) )', - 'DROP TABLE Another CASCADE CONSTRAINTS', - 'DROP SEQUENCE sq_Another_id', - 'CREATE SEQUENCE sq_Another_id', - 'CREATE TABLE Another ( +'DROP TABLE Another CASCADE CONSTRAINTS', +'DROP SEQUENCE sq_Another_id', +'CREATE SEQUENCE sq_Another_id', +'CREATE TABLE Another ( id number(10) NOT NULL, num number(10,2), PRIMARY KEY (id) )', 'DROP VIEW email_list', - 'CREATE VIEW email_list AS +'CREATE VIEW email_list AS SELECT email FROM Basic WHERE (email IS NOT NULL)', - 'ALTER TABLE Basic ADD CONSTRAINT Basic_another_id_fk FOREIGN KEY (another_id) REFERENCES Another (id)', - 'CREATE OR REPLACE TRIGGER ai_Basic_id +'ALTER TABLE Basic ADD CONSTRAINT Basic_another_id_fk FOREIGN KEY (another_id) REFERENCES Another (id)', +'CREATE OR REPLACE TRIGGER ai_Basic_id BEFORE INSERT ON Basic FOR EACH ROW WHEN ( new.id IS NULL OR new.id = 0 @@ -81,14 +81,7 @@ BEGIN FROM dual; END; ', - 'CREATE OR REPLACE TRIGGER ts_Basic_timest -BEFORE INSERT OR UPDATE ON Basic -FOR EACH ROW WHEN (new.timest IS NULL) -BEGIN - SELECT sysdate INTO :new.timest FROM dual; -END; -', - 'CREATE OR REPLACE TRIGGER ai_Another_id +'CREATE OR REPLACE TRIGGER ai_Another_id BEFORE INSERT ON Another FOR EACH ROW WHEN ( new.id IS NULL OR new.id = 0 @@ -99,7 +92,7 @@ BEGIN FROM dual; END; ', -'CREATE INDEX titleindex on Basic (title)']; +'CREATE INDEX titleindex ON Basic (title)']; is_deeply(\@sql, $want, 'Got correct Oracle statements in list context'); @@ -143,7 +136,7 @@ SELECT email FROM Basic WHERE (email IS NOT NULL); ALTER TABLE Basic ADD CONSTRAINT Basic_another_id_fk01 FOREIGN KEY (another_id) REFERENCES Another (id); -CREATE INDEX titleindex01 on Basic (title); +CREATE INDEX titleindex01 ON Basic (title); CREATE OR REPLACE TRIGGER ai_Basic_id01 BEFORE INSERT ON Basic @@ -157,14 +150,6 @@ BEGIN END; / -CREATE OR REPLACE TRIGGER ts_Basic_timest01 -BEFORE INSERT OR UPDATE ON Basic -FOR EACH ROW WHEN (new.timest IS NULL) -BEGIN - SELECT sysdate INTO :new.timest FROM dual; -END; -/ - CREATE OR REPLACE TRIGGER ai_Another_id01 BEFORE INSERT ON Another FOR EACH ROW WHEN ( diff --git a/t/51-xml-to-oracle_quoted.t b/t/51-xml-to-oracle_quoted.t index 6f3a10217..5bbacf14e 100644 --- a/t/51-xml-to-oracle_quoted.t +++ b/t/51-xml-to-oracle_quoted.t @@ -42,9 +42,9 @@ my $sql_string = $sqlt->translate( my $want = [ 'DROP TABLE "Basic" CASCADE CONSTRAINTS', - 'DROP SEQUENCE "sq_Basic_id"', - 'CREATE SEQUENCE "sq_Basic_id"', - 'CREATE TABLE "Basic" ( +'DROP SEQUENCE "sq_Basic_id"', +'CREATE SEQUENCE "sq_Basic_id"', +'CREATE TABLE "Basic" ( "id" number(10) NOT NULL, "title" varchar2(100) DEFAULT \'hello\' NOT NULL, "description" clob DEFAULT \'\', @@ -58,19 +58,19 @@ my $want = [ CONSTRAINT "u_Basic_emailuniqueindex" UNIQUE ("email"), CONSTRAINT "u_Basic_very_long_index_name_o" UNIQUE ("title") )', - 'DROP TABLE "Another" CASCADE CONSTRAINTS', - 'DROP SEQUENCE "sq_Another_id"', - 'CREATE SEQUENCE "sq_Another_id"', - 'CREATE TABLE "Another" ( +'DROP TABLE "Another" CASCADE CONSTRAINTS', +'DROP SEQUENCE "sq_Another_id"', +'CREATE SEQUENCE "sq_Another_id"', +'CREATE TABLE "Another" ( "id" number(10) NOT NULL, "num" number(10,2), PRIMARY KEY ("id") )', 'DROP VIEW "email_list"', - 'CREATE VIEW "email_list" AS +'CREATE VIEW "email_list" AS SELECT email FROM Basic WHERE (email IS NOT NULL)', - 'ALTER TABLE "Basic" ADD CONSTRAINT "Basic_another_id_fk" FOREIGN KEY ("another_id") REFERENCES "Another" ("id")', - 'CREATE OR REPLACE TRIGGER "ai_Basic_id" +'ALTER TABLE "Basic" ADD CONSTRAINT "Basic_another_id_fk" FOREIGN KEY ("another_id") REFERENCES "Another" ("id")', +'CREATE OR REPLACE TRIGGER "ai_Basic_id" BEFORE INSERT ON "Basic" FOR EACH ROW WHEN ( new."id" IS NULL OR new."id" = 0 @@ -81,14 +81,7 @@ BEGIN FROM dual; END; ', - 'CREATE OR REPLACE TRIGGER "ts_Basic_timest" -BEFORE INSERT OR UPDATE ON "Basic" -FOR EACH ROW WHEN (new."timest" IS NULL) -BEGIN - SELECT sysdate INTO :new."timest" FROM dual; -END; -', - 'CREATE OR REPLACE TRIGGER "ai_Another_id" +'CREATE OR REPLACE TRIGGER "ai_Another_id" BEFORE INSERT ON "Another" FOR EACH ROW WHEN ( new."id" IS NULL OR new."id" = 0 @@ -99,7 +92,7 @@ BEGIN FROM dual; END; ', -'CREATE INDEX "titleindex" on "Basic" ("title")']; +'CREATE INDEX "titleindex" ON "Basic" ("title")']; is_deeply(\@sql, $want, 'Got correct Oracle statements in list context'); @@ -143,7 +136,7 @@ SELECT email FROM Basic WHERE (email IS NOT NULL); ALTER TABLE "Basic" ADD CONSTRAINT "Basic_another_id_fk01" FOREIGN KEY ("another_id") REFERENCES "Another" ("id"); -CREATE INDEX "titleindex01" on "Basic" ("title"); +CREATE INDEX "titleindex01" ON "Basic" ("title"); CREATE OR REPLACE TRIGGER "ai_Basic_id01" BEFORE INSERT ON "Basic" @@ -157,14 +150,6 @@ BEGIN END; / -CREATE OR REPLACE TRIGGER "ts_Basic_timest01" -BEFORE INSERT OR UPDATE ON "Basic" -FOR EACH ROW WHEN (new."timest" IS NULL) -BEGIN - SELECT sysdate INTO :new."timest" FROM dual; -END; -/ - CREATE OR REPLACE TRIGGER "ai_Another_id01" BEFORE INSERT ON "Another" FOR EACH ROW WHEN ( diff --git a/t/54-oracle-alter-constraint.t b/t/54-oracle-alter-constraint.t new file mode 100644 index 000000000..1dd6cb5da --- /dev/null +++ b/t/54-oracle-alter-constraint.t @@ -0,0 +1,59 @@ +#!/usr/bin/perl + +use FindBin qw/$Bin/; +use Test::More; +use Test::SQL::Translator; +use Test::Exception; +use Data::Dumper; +use SQL::Translator; +use SQL::Translator::Diff; + +BEGIN { + maybe_plan(6, 'SQL::Translator::Parser::YAML', + 'SQL::Translator::Producer::Oracle'); +} + +my $schema1 = $Bin.'/data/oracle/schema_diff_d.yaml'; +my $schema2 = $Bin.'/data/oracle/schema_diff_e.yaml'; + +open my $io1, '<', $schema1 or die $!; +open my $io2, '<', $schema2 or die $!; + +my ($yaml1, $yaml2); +{ + local $/ = undef; + $yaml1 = <$io1>; + $yaml2 = <$io2>; +}; + +close $io1; +close $io2; + +my $s = SQL::Translator->new(from => 'YAML'); +$s->parser->($s,$yaml1); + +my $t = SQL::Translator->new(from => 'YAML'); +$t->parser->($t,$yaml2); + +my $d = SQL::Translator::Diff->new + ({ + output_db => 'Oracle', + target_db => 'Oracle', + source_schema => $s->schema, + target_schema => $t->schema, + }); + + +my $diff = $d->compute_differences->produce_diff_sql || die $d->error; + +ok($diff, 'Diff generated.'); + +like($diff, '/ALTER TABLE d_operator DROP CONSTRAINT foo_unique/', 'DROP constraint foo_unique generated'); + +like($diff, '/DROP CONSTRAINT other_check/', 'DROP constraint other_check generated'); + +like($diff, '/ADD CONSTRAINT other_check CHECK \(other BETWEEN 100 and 99999\)/', 'ADD check constraint generated'); + +like($diff, '/ALTER TABLE supplier DROP CONSTRAINT fk_customer/', 'DROP Foreign key constraint generated'); + +like($diff, '/DROP TABLE customer/', 'DROP TABLE customer generated'); diff --git a/t/54-oracle-alter-field.t b/t/54-oracle-alter-field.t index 5f736ccff..38b37b961 100644 --- a/t/54-oracle-alter-field.t +++ b/t/54-oracle-alter-field.t @@ -47,7 +47,7 @@ my $d = SQL::Translator::Diff->new my $diff = $d->compute_differences->produce_diff_sql || die $d->error; ok($diff, 'Diff generated.'); -like($diff, '/ALTER TABLE d_operator MODIFY \( name nvarchar2\(10\) \)/', +like($diff, '/ALTER TABLE d_operator MODIFY \( name nvarchar2\(10\) NULL \)/', 'Alter table generated.'); like($diff, '/ALTER TABLE d_operator MODIFY \( other nvarchar2\(10\) NOT NULL \)/', 'Alter table generated.'); diff --git a/t/54-oracle-sql-diff.t b/t/54-oracle-sql-diff.t new file mode 100644 index 000000000..20aba524c --- /dev/null +++ b/t/54-oracle-sql-diff.t @@ -0,0 +1,67 @@ +#!/usr/bin/perl + +use FindBin qw/$Bin/; +use Test::More; +use Test::SQL::Translator; +use Test::Exception; +use Data::Dumper; +use SQL::Translator; +use SQL::Translator::Diff; + +BEGIN { + maybe_plan(11, 'SQL::Translator::Producer::Oracle'); +} + +my $schema1 = $Bin.'/data/oracle/schema-1.5.sql'; +my $schema2 = $Bin.'/data/oracle/schema-1.6.sql'; + +open my $io1, '<', $schema1 or die $!; +open my $io2, '<', $schema2 or die $!; + +my ($yaml1, $yaml2); +{ + local $/ = undef; + $sql1 = <$io1>; + $sql2 = <$io2>; +}; + +close $io1; +close $io2; + +my $s = SQL::Translator->new(from => 'Oracle'); +$s->parser->($s,$sql1); + +my $t = SQL::Translator->new(from => 'Oracle', debug => 1); +$t->parser->($t,$sql2); + +my $d = SQL::Translator::Diff->new + ({ + output_db => 'Oracle', + source_schema => $s->schema, + target_schema => $t->schema, + sqlt_args => {quote_identifiers => 0} + }); + +my $diff = $d->compute_differences->produce_diff_sql || die $d->error; + +ok($diff, 'Diff generated.'); + +like($diff, '/CREATE TABLE t_group/', 'CREATE TABLE t_group generated'); + +like($diff, '/ALTER TABLE t_category DROP PRIMARY KEY/', 'Drop PRIMARY KEY generated'); + +like($diff, '/ALTER TABLE t_category DROP CONSTRAINT t_category_display_name/', 'DROP constraint t_category_display_name generated'); + +like($diff, '/ALTER TABLE t_user_groups DROP CONSTRAINT t_user_groups_group_id_fk/', 'DROP FOREIGN KEY constraint generated'); + +like($diff, '/DROP INDEX t_alert_roles_idx_alert_id/', 'DROP INDEX generated'); + +like($diff, '/ALTER TABLE t_message MODIFY \( alert_id number\(11\) \)/', 'MODIFY alert_id generated'); + +like($diff, '/CREATE INDEX t_user_groups_idx_user_id ON t_user_groups \(user_id\)/', 'CREATE INDEX generated'); + +like($diff, '/ALTER TABLE t_user_groups ADD CONSTRAINT t_user_groups_group_id_fk FOREIGN KEY \(group_id\) REFERENCES t_group \(group_id\) ON DELETE CASCADE/', 'ADD FOREIGN KEY constraint generated'); + +like($diff, '/ALTER TABLE t_population_group DROP CONSTRAINT t_population_group_group_role_fk/', 'DROP FOREIGN KEY before drop table generated'); + +like($diff, '/DROP TABLE t_population_group/', 'DROP TABLE generated'); \ No newline at end of file diff --git a/t/55-oracle-add-field.t b/t/55-oracle-add-drop-field.t similarity index 84% rename from t/55-oracle-add-field.t rename to t/55-oracle-add-drop-field.t index e4e2535e1..b7f74f2b9 100644 --- a/t/55-oracle-add-field.t +++ b/t/55-oracle-add-drop-field.t @@ -9,7 +9,7 @@ use SQL::Translator; use SQL::Translator::Diff; BEGIN { - maybe_plan(2, 'SQL::Translator::Parser::YAML', + maybe_plan(3, 'SQL::Translator::Parser::YAML', 'SQL::Translator::Producer::Oracle'); } @@ -46,5 +46,6 @@ my $d = SQL::Translator::Diff->new my $diff = $d->compute_differences->produce_diff_sql || die $d->error; ok($diff, 'Diff generated.'); -like($diff, '/ALTER TABLE d_operator ADD \( foo nvarchar2\(10\) NOT NULL \)/', - 'Alter table generated.'); + +like($diff, '/ALTER TABLE d_operator DROP COLUMN bar/', 'DROP column generated.'); +like($diff, '/ALTER TABLE d_operator ADD \( foo nvarchar2\(10\) NOT NULL \)/', 'ADD column generated.'); diff --git a/t/55-oracle-producer.t b/t/55-oracle-producer.t index 57e25ea5b..f22f03a16 100644 --- a/t/55-oracle-producer.t +++ b/t/55-oracle-producer.t @@ -79,7 +79,7 @@ use SQL::Translator::Producer::Oracle; ], 'correct "CREATE CONSTRAINT" SQL' ); - + my $materialized_view = SQL::Translator::Schema::View->new( name => 'matview', sql => 'SELECT id, name FROM table3', diff --git a/t/data/oracle/schema-1.5.sql b/t/data/oracle/schema-1.5.sql new file mode 100644 index 000000000..17380c5f7 --- /dev/null +++ b/t/data/oracle/schema-1.5.sql @@ -0,0 +1,213 @@ +CREATE TABLE t_category ( + category_id number(11) NOT NULL, + display_name varchar2(256) NOT NULL, + description varchar2(4000) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (category_id), + CONSTRAINT t_category_display_name UNIQUE (display_name) +); + + +CREATE SEQUENCE sq_t_message_message_id; + +CREATE TABLE t_message ( + message_id number(11) NOT NULL, + alert_id number(45) NOT NULL, + from_address varchar2(256) NOT NULL, + recipient nvarchar2(64) NOT NULL, + subject_line varchar2(512) NOT NULL, + body_text clob NOT NULL, + body_html clob NOT NULL, + short_body varchar2(160) NOT NULL, + template_id number(11) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (message_id) +); + + +CREATE TABLE t_user ( + user_id varchar2(32) NOT NULL, + name varchar2(256), + last4_pid varchar2(4) NOT NULL, + pidm number(11) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + mobile_phone varchar2(11), + mobile_phone_source varchar2(64), + reason_for_change varchar2(128), + im_id varchar2(512), + opt_in date, + opt_in_confirm date, + mobile_phone_2 varchar2(11), + PRIMARY KEY (user_id), + CONSTRAINT t_user_pidm UNIQUE (pidm) +); + + +CREATE SEQUENCE sq_t_population_group_group_; + +CREATE TABLE t_population_group ( + group_id number(11) NOT NULL, + group_name varchar2(256) NOT NULL, + group_description varchar2(256) NOT NULL, + group_role number(11), + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + group_type varchar2(256), + group_sql clob NOT NULL, + active number(1) NOT NULL, + source varchar2(256) NOT NULL, + private number(1) NOT NULL, + fpm_bldg_no varchar2(11) NOT NULL, + PRIMARY KEY (group_id) +); + + +CREATE SEQUENCE sq_t_role_role_id; + +CREATE TABLE t_role ( + role_id number(11) NOT NULL, + role_name varchar2(64) NOT NULL, + role_desc varchar2(128) NOT NULL, + PRIMARY KEY (role_id) +); + + +CREATE SEQUENCE sq_t_alert_alert_id; + +CREATE TABLE t_alert ( + alert_id number(11) NOT NULL, + category number(11) NOT NULL, + title varchar2(64) NOT NULL, + allow_email_opt_out number(1) NOT NULL, + enabled number(1) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (alert_id) +); + + +CREATE TABLE t_user_groups ( + user_id varchar2(32) NOT NULL, + group_id number(11) NOT NULL, + PRIMARY KEY (user_id, group_id) +); + + +CREATE TABLE t_user_roles ( + user_id varchar2(32) NOT NULL, + role_id number(11) NOT NULL, + PRIMARY KEY (user_id, role_id) +); + + +CREATE TABLE t_category_defaults ( + category_id number(11) NOT NULL, + user_id varchar2(32) NOT NULL, + default_email number(1) NOT NULL, + default_sms number(1) NOT NULL, + default_push number(1) NOT NULL, + default_im number(1) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (category_id, user_id) +); + + +CREATE TABLE t_alert_roles ( + alert_id number(11) NOT NULL, + role_id number(11) NOT NULL, + PRIMARY KEY (alert_id, role_id) +); + +ALTER TABLE t_alert ADD CONSTRAINT t_alert_category_fk FOREIGN KEY (category) REFERENCES t_category (category_id); + +ALTER TABLE t_user_groups ADD CONSTRAINT t_user_groups_group_id_fk FOREIGN KEY (group_id) REFERENCES t_population_group (group_id) ON DELETE CASCADE; + +ALTER TABLE t_user_roles ADD CONSTRAINT t_user_roles_role_id_fk FOREIGN KEY (role_id) REFERENCES t_role (role_id) ON DELETE CASCADE; + +ALTER TABLE t_category_defaults ADD CONSTRAINT t_category_defaults_category FOREIGN KEY (category_id) REFERENCES t_category (category_id); + +ALTER TABLE t_category_defaults ADD CONSTRAINT t_category_defaults_user_id FOREIGN KEY (user_id) REFERENCES t_user (user_id); + +ALTER TABLE t_alert_roles ADD CONSTRAINT t_alert_roles_alert_id_fk FOREIGN KEY (alert_id) REFERENCES t_alert (alert_id) ON DELETE CASCADE; + +ALTER TABLE t_alert_roles ADD CONSTRAINT t_alert_roles_role_id_fk FOREIGN KEY (role_id) REFERENCES t_role (role_id); + +ALTER TABLE t_population_group ADD CONSTRAINT t_population_group_group_role_fk FOREIGN KEY (group_role) REFERENCES t_role (role_id); + +CREATE INDEX t_alert_idx_category on t_alert (category); + +CREATE INDEX t_user_groups_idx_group_id on t_user_groups (group_id); + +CREATE INDEX t_user_roles_idx_user_id on t_user_roles (user_id); + +CREATE INDEX t_user_roles_idx_role_id on t_user_roles (role_id); + +CREATE INDEX t_category_defaults_idx_cate on t_category_defaults (category_id); + +CREATE INDEX t_category_defaults_idx_acce on t_category_defaults (user_id); + +CREATE INDEX t_alert_roles_idx_alert_id on t_alert_roles (alert_id); + +CREATE INDEX t_alert_roles_idx_role_id on t_alert_roles (role_id); + +CREATE OR REPLACE TRIGGER ai_t_message_message_id +BEFORE INSERT ON t_message +FOR EACH ROW WHEN ( + new.message_id IS NULL OR new.message_id = 0 +) +BEGIN + SELECT sq_t_message_message_id.nextval + INTO :new.message_id + FROM dual; +END; +/ + +CREATE OR REPLACE TRIGGER ai_t_population_group_group_ +BEFORE INSERT ON t_population_group +FOR EACH ROW WHEN ( + new.group_id IS NULL OR new.group_id = 0 +) +BEGIN + SELECT sq_t_population_group_group_.nextval + INTO :new.group_id + FROM dual; +END; +/ + +CREATE OR REPLACE TRIGGER ai_t_role_role_id +BEFORE INSERT ON t_role +FOR EACH ROW WHEN ( + new.role_id IS NULL OR new.role_id = 0 +) +BEGIN + SELECT sq_t_role_role_id.nextval + INTO :new.role_id + FROM dual; +END; +/ + +CREATE OR REPLACE TRIGGER ai_t_alert_alert_id +BEFORE INSERT ON t_alert +FOR EACH ROW WHEN ( + new.alert_id IS NULL OR new.alert_id = 0 +) +BEGIN + SELECT sq_t_alert_alert_id.nextval + INTO :new.alert_id + FROM dual; +END; +/ \ No newline at end of file diff --git a/t/data/oracle/schema-1.6.sql b/t/data/oracle/schema-1.6.sql new file mode 100644 index 000000000..3b7be9cd4 --- /dev/null +++ b/t/data/oracle/schema-1.6.sql @@ -0,0 +1,201 @@ +CREATE TABLE t_category ( + id number(11) NOT NULL, + display_name varchar2(256) NOT NULL, + description varchar2(4000) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (id) +); + +CREATE SEQUENCE sq_t_group_group_id; + +CREATE TABLE t_group ( + group_id number(11) NOT NULL, + group_name varchar2(256) NOT NULL, + group_description varchar2(256) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + group_type varchar2(256), + group_sql clob NOT NULL, + active number(1) NOT NULL, + source varchar2(256) NOT NULL, + private number(1) NOT NULL, + fpm_bldg_no varchar2(11) NOT NULL, + PRIMARY KEY (group_id) +); + +CREATE SEQUENCE sq_t_message_message_id; + +CREATE TABLE t_message ( + message_id number(11) NOT NULL, + alert_id number(11) NOT NULL, + from_address varchar2(256) NOT NULL, + recipient varchar2(64) NOT NULL, + subject_line varchar2(512) NOT NULL, + body_text clob NOT NULL, + body_html clob NOT NULL, + short_body varchar2(160) NOT NULL, + template_id number(11) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (message_id) +); + +CREATE SEQUENCE sq_t_role_role_id; + +CREATE TABLE t_role ( + role_id number(11) NOT NULL, + role_name varchar2(64) NOT NULL, + role_desc varchar2(128) NOT NULL, + PRIMARY KEY (role_id) +); + +CREATE TABLE t_user ( + user_id varchar2(32) NOT NULL, + name varchar2(256), + last4_pid varchar2(4) NOT NULL, + pidm number(11) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + mobile_phone varchar2(11), + mobile_phone_source varchar2(64), + reason_for_change varchar2(128), + im_id varchar2(512), + opt_in date, + opt_in_confirm date, + mobile_phone_2 varchar2(11), + PRIMARY KEY (user_id), + CONSTRAINT t_user_pidm UNIQUE (pidm) +); + +CREATE SEQUENCE sq_t_alert_alert_id; + +CREATE TABLE t_alert ( + alert_id number(11) NOT NULL, + category number(11) NOT NULL, + title varchar2(64) NOT NULL, + allow_email_opt_out number(1) NOT NULL, + enabled number(1) NOT NULL, + added date DEFAULT CURRENT_TIMESTAMP NOT NULL, + added_by varchar2(32) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (alert_id) +); + +CREATE TABLE t_category_defaults ( + category_id number(11) NOT NULL, + user_id varchar2(32) NOT NULL, + default_email number(1) NOT NULL, + default_sms number(1) NOT NULL, + default_push number(1) NOT NULL, + default_im number(1) NOT NULL, + modified date, + modified_by varchar2(32), + PRIMARY KEY (category_id, user_id) +); + +CREATE TABLE t_user_groups ( + user_id varchar2(32) NOT NULL, + group_id number(11) NOT NULL, + PRIMARY KEY (user_id, group_id) +); + +CREATE TABLE t_user_roles ( + user_id varchar2(32) NOT NULL, + role_id number(11) NOT NULL, + PRIMARY KEY (user_id, role_id) +); + +CREATE TABLE t_alert_roles ( + alert_id number(11) NOT NULL, + role_id number(11) NOT NULL, + PRIMARY KEY (alert_id, role_id) +); + +ALTER TABLE t_alert ADD CONSTRAINT t_alert_category_fk FOREIGN KEY (category) REFERENCES t_category (id); + +ALTER TABLE t_category_defaults ADD CONSTRAINT t_category_defaults_category FOREIGN KEY (category_id) REFERENCES t_category (id); + +ALTER TABLE t_category_defaults ADD CONSTRAINT t_category_defaults_user_id FOREIGN KEY (user_id) REFERENCES t_user (user_id); + +ALTER TABLE t_user_groups ADD CONSTRAINT t_user_groups_group_id_fk FOREIGN KEY (group_id) REFERENCES t_group (group_id) ON DELETE CASCADE; + +ALTER TABLE t_user_roles ADD CONSTRAINT t_user_roles_role_id_fk FOREIGN KEY (role_id) REFERENCES t_role (role_id) ON DELETE CASCADE; + +ALTER TABLE t_alert_roles ADD CONSTRAINT t_alert_roles_alert_id_fk FOREIGN KEY (alert_id) REFERENCES t_alert (alert_id) ON DELETE CASCADE; + +ALTER TABLE t_alert_roles ADD CONSTRAINT t_alert_roles_role_id_fk FOREIGN KEY (role_id) REFERENCES t_role (role_id); + +CREATE INDEX t_alert_idx_category on t_alert (category); + +CREATE INDEX t_category_defaults_idx_cate on t_category_defaults (category_id); + +CREATE INDEX t_category_defaults_idx_acce on t_category_defaults (user_id); + +CREATE INDEX t_user_groups_idx_group_id on t_user_groups (group_id); + +CREATE INDEX t_user_groups_idx_user_id on t_user_groups (user_id); + +CREATE INDEX t_user_roles_idx_role_id on t_user_roles (role_id); + +CREATE INDEX t_user_roles_idx_user_id on t_user_roles (user_id); + +CREATE INDEX t_alert_roles_idx_role_id on t_alert_roles (role_id); + +CREATE OR REPLACE TRIGGER ai_t_group_group_id +BEFORE INSERT ON t_group +FOR EACH ROW WHEN ( + new.group_id IS NULL OR new.group_id = 0 +) +BEGIN + SELECT sq_t_group_group_id.nextval + INTO :new.group_id + FROM dual; +END; +/ + +CREATE OR REPLACE TRIGGER ai_t_message_message_id +BEFORE INSERT ON t_message +FOR EACH ROW WHEN ( + new.message_id IS NULL OR new.message_id = 0 +) +BEGIN + SELECT sq_t_message_message_id.nextval + INTO :new.message_id + FROM dual; +END; +/ + +CREATE OR REPLACE TRIGGER ai_t_role_role_id +BEFORE INSERT ON t_role +FOR EACH ROW WHEN ( + new.role_id IS NULL OR new.role_id = 0 +) +BEGIN + SELECT sq_t_role_role_id.nextval + INTO :new.role_id + FROM dual; +END; +/ + +CREATE OR REPLACE TRIGGER ai_t_alert_alert_id +BEFORE INSERT ON t_alert +FOR EACH ROW WHEN ( + new.alert_id IS NULL OR new.alert_id = 0 +) +BEGIN + SELECT sq_t_alert_alert_id.nextval + INTO :new.alert_id + FROM dual; +END; +/ + diff --git a/t/data/oracle/schema_diff_b.yaml b/t/data/oracle/schema_diff_b.yaml index 42f71ba1f..cb9699e8d 100644 --- a/t/data/oracle/schema_diff_b.yaml +++ b/t/data/oracle/schema_diff_b.yaml @@ -29,11 +29,22 @@ schema: order: 58 size: - 0 + bar: + data_type: varchar2 + default_value: ~ + extra: {} + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: bar + order: 61 + size: + - 3 name: data_type: nvarchar2 default_value: ~ extra: {} - is_nullable: 0 + is_nullable: 1 is_primary_key: 0 is_unique: 0 name: name diff --git a/t/data/oracle/schema_diff_d.yaml b/t/data/oracle/schema_diff_d.yaml new file mode 100644 index 000000000..e73e578d7 --- /dev/null +++ b/t/data/oracle/schema_diff_d.yaml @@ -0,0 +1,122 @@ +--- +schema: + procedures: {} + tables: + d_operator: + constraints: + - deferrable: 1 + expression: '' + fields: + - id_operator + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - fields: foo + name: 'foo_unique' + type: UNIQUE + - fields: other + name: 'other_check' + type: CHECK + expression: other BETWEEN 1 and 99999 + fields: + id_operator: + data_type: integer + default_value: ~ + extra: {} + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id_operator + order: 58 + size: + - 0 + name: + data_type: nvarchar2 + default_value: ~ + extra: {} + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 59 + size: + - 10 + foo: + data_type: nvarchar2 + default_value: ~ + extra: {} + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: foo + order: 60 + size: + - 10 + other: + data_type: integer + default_value: ~ + extra: {} + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: other + order: 61 + size: + - 5 + name: d_operator + order: 11 + supplier: + constraints: + - fields: cust_id + name: fk_customer + reference_table: customer + reference_fields: + - customer_id + type: FOREIGN KEY + fields: + id: + data_type: integer + is_nullable: 0 + is_primary_key: 1 + size: 11 + name: id + order: 62 + cust_id: + data_type: integer + is_nullable: 1 + is_primary_key: 0 + size: 11 + name: cust_id + order: 63 + supplier_name: + data_type: nvarchar2 + is_nullable: 0 + is_primary_key: 0 + size: 256 + name: supplier_name + order: 64 + name: supplier + customer: + fields: + customer_id: + data_type: integer + is_nullable: 0 + is_primary_key: 1 + size: 11 + name: customer_id + order: 65 + customer_name: + data_type: nvarchar2 + is_nullable: 0 + is_primary_key: 0 + size: 256 + name: customer_name + order: 66 + name: customer + order: 12 \ No newline at end of file diff --git a/t/data/oracle/schema_diff_e.yaml b/t/data/oracle/schema_diff_e.yaml new file mode 100644 index 000000000..a262c33f1 --- /dev/null +++ b/t/data/oracle/schema_diff_e.yaml @@ -0,0 +1,94 @@ +--- +schema: + procedures: {} + tables: + d_operator: + constraints: + - deferrable: 1 + expression: '' + fields: + - id_operator + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - fields: other + name: 'other_check' + type: CHECK + expression: other BETWEEN 100 and 99999 + fields: + id_operator: + data_type: integer + default_value: ~ + extra: {} + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id_operator + order: 58 + size: + - 0 + name: + data_type: nvarchar2 + default_value: ~ + extra: {} + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 59 + size: + - 10 + foo: + data_type: nvarchar2 + default_value: ~ + extra: {} + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: foo + order: 60 + size: + - 10 + other: + data_type: integer + default_value: ~ + extra: {} + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: other + order: 61 + size: + - 5 + name: d_operator + order: 11 + supplier: + fields: + id: + data_type: integer + is_nullable: 0 + is_primary_key: 1 + size: 11 + name: id + order: 62 + cust_id: + data_type: integer + is_nullable: 1 + is_primary_key: 0 + size: 11 + name: cust_id + order: 63 + supplier_name: + data_type: nvarchar2 + is_nullable: 0 + is_primary_key: 0 + size: 256 + name: supplier_name + order: 65 + name: supplier