X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FDeploymentHandler.pm;h=e78d07f87f000126308fa1004e16c1a30743788e;hb=cf400f483c5280a6d302f715b0d730ae829a4523;hp=a8380a736189b4290bec58dd91bb642bd335a1af;hpb=12fdd4619a7f344b916541a48ac4f7cd86c3eaa9;p=dbsrgits%2FDBIx-Class-DeploymentHandler.git diff --git a/lib/DBIx/Class/DeploymentHandler.pm b/lib/DBIx/Class/DeploymentHandler.pm index a8380a7..e78d07f 100644 --- a/lib/DBIx/Class/DeploymentHandler.pm +++ b/lib/DBIx/Class/DeploymentHandler.pm @@ -5,66 +5,98 @@ use Method::Signatures::Simple; require DBIx::Class::Schema; # loaded for type constraint require DBIx::Class::Storage; # loaded for type constraint require DBIx::Class::ResultSet; # loaded for type constraint -use Carp 'carp'; +use Carp::Clan '^DBIx::Class::DeploymentHandler'; +use SQL::Translator; + +BEGIN { + use Moose::Util::TypeConstraints; + subtype 'DBIx::Class::DeploymentHandler::Databases' + => as 'ArrayRef[Str]'; + + coerce 'DBIx::Class::DeploymentHandler::Databases' + => from 'Str' + => via { [$_] }; + no Moose::Util::TypeConstraints; +} has schema => ( - isa => 'DBIx::Class::Schema', - is => 'ro', - required => 1, - handles => [qw{schema_version}], + isa => 'DBIx::Class::Schema', + is => 'ro', + required => 1, + handles => [qw( ddl_filename schema_version )], ); has upgrade_directory => ( - isa => 'Str', - is => 'ro', - required => 1, - default => 'sql', + isa => 'Str', + is => 'ro', + required => 1, + default => 'sql', ); has backup_directory => ( - isa => 'Str', - is => 'ro', + isa => 'Str', + is => 'ro', ); has storage => ( - isa => 'DBIx::Class::Storage', - is => 'ro', - lazy_build => 1, + isa => 'DBIx::Class::Storage', + is => 'ro', + lazy_build => 1, ); -method _build_storage { $self->schema->storage } +method _build_storage { + my $s = $self->schema->storage; + $s->_determine_driver; + $s +} has _filedata => ( - isa => 'Str', - is => 'rw', + isa => 'ArrayRef[Str]', + is => 'rw', ); has do_backup => ( - isa => 'Bool', - is => 'ro', - default => undef, + isa => 'Bool', + is => 'ro', + default => undef, ); has do_diff_on_init => ( - isa => 'Bool', - is => 'ro', - default => undef, + isa => 'Bool', + is => 'ro', + default => undef, ); has version_rs => ( - isa => 'DBIx::Class::ResultSet', - is => 'ro', - lazy_build => 1, - handles => [qw( is_installed db_version )], + isa => 'DBIx::Class::ResultSet', + is => 'ro', + lazy_build => 1, + handles => [qw( is_installed db_version )], ); -method _build_version_rs { $self->schema->resultset('VersionResult') } +has databases => ( + coerce => 1, + isa => 'DBIx::Class::DeploymentHandler::Databases', + is => 'ro', + default => sub { [qw( MySQL SQLite PostgreSQL )] }, +); + +has sqltargs => ( + isa => 'HashRef', + is => 'ro', + default => sub { {} }, +); + +method _build_version_rs { + $self->schema->set_us_up_the_bomb; + $self->schema->resultset('__VERSION') +} method backup { $self->storage->backup($self->backup_directory) } method install($new_version) { carp 'Install not possible as versions table already exists in database' - unless $self->is_installed; + if $self->is_installed; $new_version ||= $self->schema_version; @@ -72,29 +104,31 @@ method install($new_version) { $self->schema->deploy; $self->version_rs->create({ - version => $new_version, - # ddl => $ddl, - # upgrade_sql => $upgrade_sql, + version => $new_version, + # ddl => $ddl, + # upgrade_sql => $upgrade_sql, }); } } method create_upgrade_path { } -method ordered_schema_versions { } +method ordered_schema_versions { undef } method upgrade { my $db_version = $self->db_version; my $schema_version = $self->schema_version; unless ($db_version) { - carp 'Upgrade not possible as database is unversioned. Please call install first.'; - return; + # croak? + carp 'Upgrade not possible as database is unversioned. Please call install first.'; + return; } if ( $db_version eq $schema_version ) { - carp "Upgrade not necessary\n"; - return; + # croak? + carp "Upgrade not necessary\n"; + return; } my @version_list = $self->ordered_schema_versions || @@ -102,12 +136,12 @@ method upgrade { # remove all versions in list above the required version while ( @version_list && ( $version_list[-1] ne $schema_version ) ) { - pop @version_list; + pop @version_list; } # remove all versions in list below the current version while ( @version_list && ( $version_list[0] ne $db_version ) ) { - shift @version_list; + shift @version_list; } # check we have an appropriate list of versions @@ -115,23 +149,18 @@ method upgrade { # do sets of upgrade while ( @version_list >= 2 ) { - $self->upgrade_single_step( $version_list[0], $version_list[1] ); - shift @version_list; + $self->upgrade_single_step( $version_list[0], $version_list[1] ); + shift @version_list; } } method upgrade_single_step($db_version, $target_version) { if ($db_version eq $target_version) { + # croak? carp "Upgrade not necessary\n"; return; } - # strangely the first time this is called can - # differ to subsequent times. so we call it - # here to be sure. - # XXX - just fix it - $self->storage->sqlt_type; - my $upgrade_file = $self->ddl_filename( $self->storage->sqlt_type, $target_version, @@ -142,103 +171,165 @@ method upgrade_single_step($db_version, $target_version) { $self->create_upgrade_path({ upgrade_file => $upgrade_file }); unless (-f $upgrade_file) { + # croak? carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n"; return; } carp "DB version ($db_version) is lower than the schema version (".$self->schema_version."). Attempting upgrade.\n"; - # backup if necessary then apply upgrade - $self->_filedata($self->_read_sql_file($upgrade_file)); + $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22 $self->backup if $self->do_backup; $self->schema->txn_do(sub { $self->do_upgrade }); - # set row in dbix_class_schema_versions table $self->version_rs->create({ - version => $target_version, - # ddl => $ddl, - # upgrade_sql => $upgrade_sql, + version => $target_version, + # ddl => $ddl, + # upgrade_sql => $upgrade_sql, }); } -method do_upgrade { $self->run_upgrade(qr/.*?/) } +method create_ddl_dir($version, $preversion) { + my $schema = $self->schema; + my $databases = $self->databases; + my $dir = $self->upgrade_directory; + my $sqltargs = $self->sqltargs; + unless( -d $dir ) { + carp "Upgrade directory $dir does not exist, using ./\n"; + $dir = "./"; + } -method run_upgrade($stm) { - return unless $self->_filedata; - my @statements = grep { $_ =~ $stm } @{$self->_filedata}; + my $schema_version = $schema->schema_version || '1.x'; + $version ||= $schema_version; - for (@statements) { - $self->storage->debugobj->query_start($_) if $self->storage->debug; - $self->apply_statement($_); - $self->storage->debugobj->query_end($_) if $self->storage->debug; - } -} + $sqltargs = { + add_drop_table => 1, + ignore_constraint_names => 1, + ignore_index_names => 1, + %{$sqltargs || {}} + }; + + my $sqlt = SQL::Translator->new( $sqltargs ); + + $sqlt->parser('SQL::Translator::Parser::DBIx::Class'); + my $sqlt_schema = $sqlt->translate({ data => $schema }) + or $self->throw_exception ($sqlt->error); + + foreach my $db (@$databases) { + $sqlt->reset; + $sqlt->{schema} = $sqlt_schema; + $sqlt->producer($db); + + my $filename = $self->ddl_filename($db, $version, $dir); + if (-e $filename && ($version eq $schema_version )) { + # if we are dumping the current version, overwrite the DDL + carp "Overwriting existing DDL file - $filename"; + unlink $filename; + } -method apply_statement($statement) { - $self->storage->dbh->do($_) or carp "SQL was: $_" -} + my $output = $sqlt->translate; + if(!$output) { + carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")"); + next; + } + my $file; + unless( open $file, q(>), $filename ) { + $self->throw_exception("Can't open $filename for writing ($!)"); + next; + } + print {$file} $output; + close $file; -sub _create_db_to_schema_diff { - my $self = shift; + next unless $preversion; - my %driver_to_db_map = ( - 'mysql' => 'MySQL' - ); + require SQL::Translator::Diff; - my $db = $driver_to_db_map{$self->storage->dbh->{Driver}{Name}}; - unless ($db) { - print "Sorry, this is an unsupported DB\n"; - return; - } + my $prefilename = $self->ddl_filename($db, $preversion, $dir); + unless(-e $prefilename) { + carp("No previous schema file found ($prefilename)"); + next; + } - $self->throw_exception($self->storage->_sqlt_version_error) - unless $self->storage->_sqlt_version_ok; + my $diff_file = $self->ddl_filename($db, $version, $dir, $preversion); + if(-e $diff_file) { + carp("Overwriting existing diff file - $diff_file"); + unlink $diff_file; + } - my $db_tr = SQL::Translator->new({ - add_drop_table => 1, - parser => 'DBI', - parser_args => { dbh => $self->storage->dbh }, - producer => $db, - }); + my $source_schema; + { + my $t = SQL::Translator->new({ + %{$sqltargs}, + debug => 0, + trace => 0, + }); - my $dbic_tr = SQL::Translator->new({ - parser => 'SQL::Translator::Parser::DBIx::Class', - data => $self, - producer => $db, - }); + $t->parser( $db ) # could this really throw an exception? + or $self->throw_exception ($t->error); - $db_tr->schema->name('db_schema'); - $dbic_tr->schema->name('dbic_schema'); + my $out = $t->translate( $prefilename ) + or $self->throw_exception ($t->error); - # is this really necessary? - foreach my $tr ($db_tr, $dbic_tr) { - my $data = $tr->data; - $tr->parser->($tr, $$data); - } + $source_schema = $t->schema; - my $diff = SQL::Translator::Diff::schema_diff( - $db_tr->schema, $db, - $dbic_tr->schema, $db, { - ignore_constraint_names => 1, - ignore_index_names => 1, - caseopt => 1, + $source_schema->name( $prefilename ) + unless $source_schema->name; } - ); - my $filename = $self->ddl_filename( - $db, - $self->schema_version, - $self->upgrade_directory, - 'PRE', - ); + # The "new" style of producers have sane normalization and can support + # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't + # And we have to diff parsed SQL against parsed SQL. + my $dest_schema = $sqlt_schema; + + unless ( "SQL::Translator::Producer::$db"->can('preprocess_schema') ) { + my $t = SQL::Translator->new({ + %{$sqltargs}, + debug => 0, + trace => 0, + }); + + $t->parser( $db ) # could this really throw an exception? + or $self->throw_exception ($t->error); - open my $file, '>', $filename - or $self->throw_exception("Can't open $filename for writing ($!)"); - print {$file} $diff; - close $file; + my $out = $t->translate( $filename ) + or $self->throw_exception ($t->error); - carp "WARNING: There may be differences between your DB and your DBIC schema.\n" . - "Please review and if necessary run the SQL in $filename to sync your DB.\n"; + $dest_schema = $t->schema; + + $dest_schema->name( $filename ) + unless $dest_schema->name; + } + + my $diff = SQL::Translator::Diff::schema_diff( + $source_schema, $db, + $dest_schema, $db, + $sqltargs + ); + unless(open $file, q(>), $diff_file) { + $self->throw_exception("Can't write to $diff_file ($!)"); + next; + } + print {$file} $diff; + close $file; + } +} + +method do_upgrade { $self->run_upgrade(qr/.*?/) } + +method run_upgrade($stm) { + return unless $self->_filedata; + my @statements = grep { $_ =~ $stm } @{$self->_filedata}; + + for (@statements) { + $self->storage->debugobj->query_start($_) if $self->storage->debug; + $self->apply_statement($_); + $self->storage->debugobj->query_end($_) if $self->storage->debug; + } +} + +method apply_statement($statement) { + # croak? + $self->storage->dbh->do($_) or carp "SQL was: $_" } method _read_sql_file($file) { @@ -259,3 +350,7 @@ method _read_sql_file($file) { } 1; + +__END__ + +vim: ts=2,sw=2,expandtab