my $tar_table = $target_schema->get_table($table)
|| $source_schema->get_table($table);
- $DB::single = 1 if $table eq 'deleted';
push @diffs, $batch_alter->($tar_table,
{ map {
$func_map{$_} => $self->table_diff_hash->{$table}{$_}
next;
}
- ## field exists, something changed.
- if ( !$tar_table_field->equals($src_table_field, $self->case_insensitive) ) {
+ # field exists, something changed. This is a bit complex. Parsers can
+ # normalize types, but only some of them do, so compare the normalized and
+ # parsed types for each field to each other
+ if ( !$tar_table_field->equals($src_table_field, $self->case_insensitive) &&
+ !$tar_table_field->equals($src_table_field->parsed_field, $self->case_insensitive) &&
+ !$tar_table_field->parsed_field->equals($src_table_field, $self->case_insensitive) &&
+ !$tar_table_field->parsed_field->equals($src_table_field->parsed_field, $self->case_insensitive) ) {
# Some producers might need src field to diff against
push @{$self->table_diff_hash->{$tar_table}{fields_to_alter}}, [ $src_table_field, $tar_table_field ];
use Data::Dumper;
use Parse::RecDescent;
use Exporter;
+use Storable qw(dclone);
use base qw(Exporter);
@EXPORT_OK = qw(parse);
insert : /insert/i statement_body "$delimiter"
delimiter : /delimiter/i /[\S]+/
- { $delimiter = $item[2] }
+ { $delimiter = $item[2] }
empty_statement : "$delimiter"
}
create : CREATE /trigger/i NAME not_delimiter "$delimiter"
- {
- @table_comments = ();
- }
+ {
+ @table_comments = ();
+ }
create : CREATE PROCEDURE NAME not_delimiter "$delimiter"
- {
- @table_comments = ();
+ {
+ @table_comments = ();
my $func_name = $item[3];
my $owner = '';
my $sql = "$item[1] $item[2] $item[3] $item[4]";
$procedures{ $func_name }{'name'} = $func_name;
$procedures{ $func_name }{'owner'} = $owner;
$procedures{ $func_name }{'sql'} = $sql;
- }
+ }
PROCEDURE : /procedure/i
- | /function/i
+ | /function/i
create : CREATE algorithm /view/i NAME not_delimiter "$delimiter"
- {
- @table_comments = ();
+ {
+ @table_comments = ();
my $view_name = $item[4];
my $sql = "$item[1] $item[2] $item[3] $item[4] $item[5]";
$views{ $view_name }{'order'} = ++$view_order;
$views{ $view_name }{'name'} = $view_name;
$views{ $view_name }{'sql'} = $sql;
- }
+ }
algorithm : /algorithm/i /=/ WORD
- {
- $return = "$item[1]=$item[3]";
- }
+ {
+ $return = "$item[1]=$item[3]";
+ }
not_delimiter : /.*?(?=$delimiter)/is
$list = [];
}
- if ( @{ $size || [] } == 0 && !$thisparser->{local}{sqlt_parser_args}{no_default_sizes} ) {
- if ( lc $type eq 'tinyint' ) {
- $size = 4;
- }
- elsif ( lc $type eq 'smallint' ) {
- $size = 6;
- }
- elsif ( lc $type eq 'mediumint' ) {
- $size = 9;
- }
- elsif ( $type =~ /^int(eger)?$/i ) {
- $type = 'int';
- $size = 11;
- }
- elsif ( lc $type eq 'bigint' ) {
- $size = 20;
- }
- elsif (
- lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/
- ) {
- $size = [8,2];
- }
- }
-
- if ( $type =~ /^tiny(text|blob)$/i ) {
- $size = 255;
- }
- elsif ( $type =~ /^(blob|text)$/i ) {
- $size = 65_535;
- }
- elsif ( $type =~ /^medium(blob|text)$/i ) {
- $size = 16_777_215;
- }
- elsif ( $type =~ /^long(blob|text)$/i ) {
- $size = 4_294_967_295;
- }
$return = {
type => $type,
{ 'NULL' }
CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
- | /now\(\)/i
- { 'CURRENT_TIMESTAMP' }
-
+ | /now\(\)/i
+ { 'CURRENT_TIMESTAMP' }
+
END_OF_GRAMMAR
# -------------------------------------------------------------------
return $translator->error("Error instantiating Parse::RecDescent ".
"instance: Bad grammer");
}
-
- # This is the only way to get args into the productions/actions
- $parser->{local}{sqlt_parser_args} = $translator->parser_args;
# Preprocess for MySQL-specific and not-before-version comments from mysqldump
my $parser_version = $translator->parser_args->{mysql_parser_version} || DEFAULT_PARSER_VERSION;
$table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
for my $qual ( qw[ binary unsigned zerofill list collate ],
- 'character set', 'on update' ) {
+ 'character set', 'on update' ) {
if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
next if ref $val eq 'ARRAY' && !@$val;
$field->extra( $qual, $val );
$cdata->{'fields'} ||= [ $field->name ];
push @{ $tdata->{'constraints'} }, $cdata;
}
+
}
for my $idata ( @{ $tdata->{'indices'} || [] } ) {
on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
) or die $table->error;
}
+
+ # After the constrains and PK/idxs have been created, we normalize fields
+ normalize_field($_) for $table->get_fields;
}
my @procedures = sort {
$result->{procedures}->{ $a }->{'order'} <=> $result->{procedures}->{ $b }->{'order'}
} keys %{ $result->{procedures} };
foreach my $proc_name (@procedures) {
- $schema->add_procedure(
- name => $proc_name,
- owner => $result->{procedures}->{$proc_name}->{owner},
- sql => $result->{procedures}->{$proc_name}->{sql},
- );
+ $schema->add_procedure(
+ name => $proc_name,
+ owner => $result->{procedures}->{$proc_name}->{owner},
+ sql => $result->{procedures}->{$proc_name}->{sql},
+ );
}
my @views = sort {
$result->{views}->{ $a }->{'order'} <=> $result->{views}->{ $b }->{'order'}
} keys %{ $result->{views} };
foreach my $view_name (keys %{ $result->{views} }) {
- $schema->add_view(
- name => $view_name,
- sql => $result->{views}->{$view_name}->{sql},
- );
+ $schema->add_view(
+ name => $view_name,
+ sql => $result->{views}->{$view_name}->{sql},
+ );
}
return 1;
}
+# Takes a field, and returns
+sub normalize_field {
+ my ($field) = @_;
+ my ($size, $type, $list, $changed) = @_;
+
+ $size = $field->size;
+ $type = $field->data_type;
+ $list = $field->extra->{list} || [];
+
+ if ( !ref $size && $size eq 0 ) {
+ if ( lc $type eq 'tinyint' ) {
+ $changed = $size != 4;
+ $size = 4;
+ }
+ elsif ( lc $type eq 'smallint' ) {
+ $changed = $size != 6;
+ $size = 6;
+ }
+ elsif ( lc $type eq 'mediumint' ) {
+ $changed = $size != 9;
+ $size = 9;
+ }
+ elsif ( $type =~ /^int(eger)?$/i ) {
+ $changed = $size != 11 || $type ne 'int';
+ $type = 'int';
+ $size = 11;
+ }
+ elsif ( lc $type eq 'bigint' ) {
+ $changed = $size != 20;
+ $size = 20;
+ }
+ elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
+ my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
+ $changed = @$old_size != 2 && $old_size->[0] != 8 && $old_size->[1] != 2;
+ $size = [8,2];
+ }
+ }
+
+ if ( $type =~ /^tiny(text|blob)$/i ) {
+ $changed = $size != 255;
+ $size = 255;
+ }
+ elsif ( $type =~ /^(blob|text)$/i ) {
+ $changed = $size != 65_535;
+ $size = 65_535;
+ }
+ elsif ( $type =~ /^medium(blob|text)$/i ) {
+ $changed = $size != 16_777_215;
+ $size = 16_777_215;
+ }
+ elsif ( $type =~ /^long(blob|text)$/i ) {
+ $changed = $size != 4_294_967_295;
+ $size = 4_294_967_295;
+ }
+ $DB::single = 1 if $field->name eq 'employee_id';
+ if ($changed) {
+ # We only want to clone the field, not *everything*
+ { local $field->{table} = undef;
+ $field->parsed_field(dclone($field));
+ $field->parsed_field->{table} = $field->table;
+ }
+ $field->size($size);
+ $field->data_type($type);
+ $field->extra->{list} = $list if @$list;
+ }
+}
+
1;
# -------------------------------------------------------------------
use Test::SQL::Translator qw(maybe_plan);
BEGIN {
- maybe_plan(235, "SQL::Translator::Parser::MySQL");
+ maybe_plan(228, "SQL::Translator::Parser::MySQL");
SQL::Translator::Parser::MySQL->import('parse');
}
like($proc2->sql, qr/CREATE PROCEDURE sp_update_security_acl/, "Detected procedure sp_update_security_acl");
}
-{
- my $tr = SQL::Translator->new({parser_args => { no_default_sizes => 1 } });
- my $data = q|create table sessions (
- age int
- );|;
-
- my $val = parse($tr, $data);
- my $schema = $tr->schema;
- is( $schema->is_valid, 1, 'Schema is valid' );
- my @tables = $schema->get_tables;
- is( scalar @tables, 1, 'Right number of tables (1)' );
- my $table = shift @tables;
- is( $table->name, 'sessions', 'Found "sessions" table' );
-
- my @fields = $table->get_fields;
- is( scalar @fields, 1, 'Right number of fields (1)' );
- my $f1 = shift @fields;
- is( $f1->name, 'age', 'First field name is "id"' );
- is( $f1->data_type, 'int', 'Type is "int"' );
- is( $f1->size, 0, 'No default size' );
-
-}
use Test::Differences;
use Test::SQL::Translator qw(maybe_plan);
-plan tests => 4;
+plan tests => 5;
use_ok('SQL::Translator::Diff') or die "Cannot continue\n";
my $tr = SQL::Translator->new;
-my ( $source_schema, $target_schema ) = map {
+my ( $source_schema, $target_schema, $parsed_sql_schema ) = map {
my $t = SQL::Translator->new;
$t->parser( 'YAML' )
or die $tr->error;
$schema->name( $_ );
}
($schema);
-} (qw/create1.yml create2.yml/);
+} (qw( create1.yml create2.yml ));
# Test for differences
my $out = SQL::Translator::Diff::schema_diff( $source_schema, 'MySQL', $target_schema, 'MySQL', { no_batch_alters => 1} );
COMMIT;
## END OF DIFF
-#die $out;
-
$out = SQL::Translator::Diff::schema_diff($source_schema, 'MySQL', $target_schema, 'MySQL',
{ ignore_index_names => 1,
ignore_constraint_names => 1
## END OF DIFF
-=cut
+{
+ my $t = SQL::Translator->new;
+ $t->parser( 'MySQL' )
+ or die $tr->error;
+ my $out = $t->translate( catfile($Bin, qw/data mysql create.sql/ ) )
+ or die $tr->error;
+
+ my $schema = $t->schema;
+ unless ( $schema->name ) {
+ $schema->name( 'create.sql' );
+ }
+
+ # Now lets change the type of one of the 'integer' columns so that it
+ # matches what the mysql parser sees for '<col> interger'.
+ my $field = $target_schema->get_table('employee')->get_field('employee_id');
+ $field->data_type('integer');
+ $field->size(0);
+ $out = SQL::Translator::Diff::schema_diff($schema, 'MySQL', $target_schema, 'MySQL' );
+ eq_or_diff($out, <<'## END OF DIFF', "No differences found");
+-- Convert schema 'create.sql' to 'create2.yml':
+
+BEGIN TRANSACTION;
+
+SET foreign_key_checks=0;
+
+
+CREATE TABLE added (
+ id integer(11)
+);
+
+
+SET foreign_key_checks=1;
+
+
+ALTER TABLE employee DROP FOREIGN KEY FK5302D47D93FE702E,
+ DROP COLUMN job_title,
+ ADD CONSTRAINT FK5302D47D93FE702E_diff_1 FOREIGN KEY (employee_id) REFERENCES person (person_id);
+ALTER TABLE person DROP UNIQUE UC_age_name,
+ DROP INDEX u_name,
+ ADD COLUMN is_rock_star tinyint(4) DEFAULT '1',
+ CHANGE COLUMN person_id person_id integer(11) NOT NULL auto_increment,
+ CHANGE COLUMN name name varchar(20) NOT NULL,
+ CHANGE COLUMN age age integer(11) DEFAULT '18',
+ CHANGE COLUMN iq iq integer(11) DEFAULT '0',
+ CHANGE COLUMN description physical_description text,
+ ADD UNIQUE INDEX unique_name (name),
+ ADD UNIQUE UC_person_id (person_id),
+ ADD UNIQUE UC_age_name (age, name),
+ ENGINE=InnoDB;
+DROP TABLE deleted;
+
+COMMIT;
+## END OF DIFF
+}