X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FDeploymentHandler%2FDeployMethod%2FSQL%2FTranslator.pm;h=c45dafdb0c226432539c71831dded86540d6931a;hb=91adde755e5808a1ec12bcf00e683e3754964cc9;hp=1082ab2a0a8ee0feb273ee46c17908d0685fe492;hpb=bcc722970a94f2ba2debdeb630168bde1f8a6830;p=dbsrgits%2FDBIx-Class-DeploymentHandler.git diff --git a/lib/DBIx/Class/DeploymentHandler/DeployMethod/SQL/Translator.pm b/lib/DBIx/Class/DeploymentHandler/DeployMethod/SQL/Translator.pm index 1082ab2..c45dafd 100644 --- a/lib/DBIx/Class/DeploymentHandler/DeployMethod/SQL/Translator.pm +++ b/lib/DBIx/Class/DeploymentHandler/DeployMethod/SQL/Translator.pm @@ -1,6 +1,8 @@ package DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator; use Moose; +# ABSTRACT: Manage your SQL and Perl migrations in nicely laid out directories + use autodie; use Carp qw( carp croak ); @@ -22,7 +24,6 @@ has schema => ( isa => 'DBIx::Class::Schema', is => 'ro', required => 1, - handles => [qw( schema_version )], ); has storage => ( @@ -37,12 +38,12 @@ method _build_storage { $s } -has sqltargs => ( +has sql_translator_args => ( isa => 'HashRef', is => 'ro', default => sub { {} }, ); -has upgrade_directory => ( +has script_directory => ( isa => 'Str', is => 'ro', required => 1, @@ -62,8 +63,16 @@ has txn_wrap => ( default => 1, ); +has schema_version => ( + is => 'ro', + isa => 'Str', + lazy_build => 1, +); + +method _build_schema_version { $self->schema->schema_version } + method __ddl_consume_with_prefix($type, $versions, $prefix) { - my $base_dir = $self->upgrade_directory; + my $base_dir = $self->script_directory; my $main = catfile( $base_dir, $type ); my $generic = catfile( $base_dir, '_generic' ); @@ -96,12 +105,16 @@ method __ddl_consume_with_prefix($type, $versions, $prefix) { return [@files{sort keys %files}] } +method _ddl_preinstall_consume_filenames($type, $version) { + $self->__ddl_consume_with_prefix($type, [ $version ], 'preinstall') +} + method _ddl_schema_consume_filenames($type, $version) { $self->__ddl_consume_with_prefix($type, [ $version ], 'schema') } method _ddl_schema_produce_filename($type, $version) { - my $dirname = catfile( $self->upgrade_directory, $type, 'schema', $version ); + my $dirname = catfile( $self->script_directory, $type, 'schema', $version ); mkpath($dirname) unless -d $dirname; return catfile( $dirname, '001-auto.sql' ); @@ -116,7 +129,7 @@ method _ddl_schema_down_consume_filenames($type, $versions) { } method _ddl_schema_up_produce_filename($type, $versions) { - my $dir = $self->upgrade_directory; + my $dir = $self->script_directory; my $dirname = catfile( $dir, $type, 'up', join q(-), @{$versions}); mkpath($dirname) unless -d $dirname; @@ -158,22 +171,21 @@ method _run_sql_and_perl($filenames) { $storage->_query_end($line); } } elsif ( $filename =~ /^(.+)\.pl$/ ) { - my $package = $1; my $filedata = do { local( @ARGV, $/ ) = $filename; <> }; - # make the package name more palateable to perl - $package =~ s/\W/_/g; no warnings 'redefine'; - eval "package $package;\n\n$filedata"; + my $fn = eval "$filedata"; use warnings; - if (my $fn = $package->can('run')) { - $fn->($self->schema); + if ($@) { + carp "$filename failed to compile: $@"; + } elsif (ref $fn eq 'CODE') { + $fn->($self->schema) } else { - carp "$filename should define a run method that takes a schema but it didn't!"; + carp "$filename should define an anonymouse sub that takes a schema but it didn't!"; } } else { - croak "A file got to deploy that wasn't sql or perl!"; + croak "A file ($filename) got to deploy that wasn't sql or perl!"; } } @@ -184,21 +196,55 @@ method _run_sql_and_perl($filenames) { sub deploy { my $self = shift; + my $version = (shift @_ || {})->{version} || $self->schema_version; return $self->_run_sql_and_perl($self->_ddl_schema_consume_filenames( $self->storage->sqlt_type, - $self->schema_version + $version, )); } +sub preinstall { + my $self = shift; + my $args = shift; + my $version = $args->{version} || $self->schema_version; + my $storage_type = $args->{storage_type} || $self->storage->sqlt_type; + + my @files = @{$self->_ddl_preinstall_consume_filenames( + $storage_type, + $version, + )}; + + for my $filename (@files) { + # We ignore sql for now (till I figure out what to do with it) + if ( $filename =~ /^(.+)\.pl$/ ) { + my $filedata = do { local( @ARGV, $/ ) = $filename; <> }; + + no warnings 'redefine'; + my $fn = eval "$filedata"; + use warnings; + + if ($@) { + carp "$filename failed to compile: $@"; + } elsif (ref $fn eq 'CODE') { + $fn->() + } else { + carp "$filename should define an anonymous sub but it didn't!"; + } + } else { + croak "A file ($filename) got to preinstall_scripts that wasn't sql or perl!"; + } + } +} + sub _prepare_install { - my $self = shift; - my $sqltargs = { %{$self->sqltargs}, %{shift @_} }; + my $self = shift; + my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} }; my $to_file = shift; my $schema = $self->schema; my $databases = $self->databases; - my $dir = $self->upgrade_directory; - my $version = $schema->schema_version; + my $dir = $self->script_directory; + my $version = $self->schema_version; my $sqlt = SQL::Translator->new({ add_drop_table => 1, @@ -237,7 +283,7 @@ sub _resultsource_install_filename { my ($self, $source_name) = @_; return sub { my ($self, $type, $version) = @_; - my $dirname = catfile( $self->upgrade_directory, $type, 'schema', $version ); + my $dirname = catfile( $self->script_directory, $type, 'schema', $version ); mkpath($dirname) unless -d $dirname; return catfile( $dirname, "001-auto-$source_name.sql" ); @@ -245,8 +291,9 @@ sub _resultsource_install_filename { } sub install_resultsource { - my ($self, $source, $version) = @_; - + my ($self, $args) = @_; + my $source = $args->{result_source}; + my $version = $args->{version}; my $rs_install_file = $self->_resultsource_install_filename($source->source_name); @@ -261,7 +308,7 @@ sub install_resultsource { sub prepare_resultsource_install { my $self = shift; - my $source = shift; + my $source = (shift @_)->{result_source}; my $filename = $self->_resultsource_install_filename($source->source_name); $self->_prepare_install({ @@ -269,29 +316,32 @@ sub prepare_resultsource_install { }, $filename); } -sub prepare_install { +sub prepare_deploy { my $self = shift; $self->_prepare_install({}, '_ddl_schema_produce_filename'); } sub prepare_upgrade { - my ($self, $from_version, $to_version, $version_set) = @_; - $self->_prepare_changegrade($from_version, $to_version, $version_set, 'up'); + my ($self, $args) = @_; + $self->_prepare_changegrade( + $args->{from_version}, $args->{to_version}, $args->{version_set}, 'up' + ); } sub prepare_downgrade { - my ($self, $from_version, $to_version, $version_set) = @_; - - $self->_prepare_changegrade($from_version, $to_version, $version_set, 'down'); + my ($self, $args) = @_; + $self->_prepare_changegrade( + $args->{from_version}, $args->{to_version}, $args->{version_set}, 'down' + ); } method _prepare_changegrade($from_version, $to_version, $version_set, $direction) { my $schema = $self->schema; my $databases = $self->databases; - my $dir = $self->upgrade_directory; - my $sqltargs = $self->sqltargs; + my $dir = $self->script_directory; + my $sqltargs = $self->sql_translator_args; - my $schema_version = $schema->schema_version; + my $schema_version = $self->schema_version; $sqltargs = { add_drop_table => 1, @@ -399,7 +449,7 @@ method _read_sql_file($file) { sub downgrade_single_step { my $self = shift; - my $version_set = shift @_; + my $version_set = (shift @_)->{version_set}; my $sql = $self->_run_sql_and_perl($self->_ddl_schema_down_consume_filenames( $self->storage->sqlt_type, @@ -411,7 +461,7 @@ sub downgrade_single_step { sub upgrade_single_step { my $self = shift; - my $version_set = shift @_; + my $version_set = (shift @_)->{version_set}; my $sql = $self->_run_sql_and_perl($self->_ddl_schema_up_consume_filenames( $self->storage->sqlt_type, @@ -442,7 +492,102 @@ documented here is extra fun stuff or private methods. =head1 DIRECTORY LAYOUT -It's heavily based upon L. +Arguably this is the best feature of L. It's +heavily based upon L, but has some extensions and +modifications, so even if you are familiar with it, please read this. I feel +like the best way to describe the layout is with the following example: + + $sql_migration_dir + |- SQLite + | |- down + | | `- 2-1 + | | `- 001-auto.sql + | |- schema + | | `- 1 + | | `- 001-auto.sql + | `- up + | |- 1-2 + | | `- 001-auto.sql + | `- 2-3 + | `- 001-auto.sql + |- _common + | |- down + | | `- 2-1 + | | `- 002-remove-customers.pl + | `- up + | `- 1-2 + | `- 002-generate-customers.pl + |- _generic + | |- down + | | `- 2-1 + | | `- 001-auto.sql + | |- schema + | | `- 1 + | | `- 001-auto.sql + | `- up + | `- 1-2 + | |- 001-auto.sql + | `- 002-create-stored-procedures.sql + `- MySQL + |- down + | `- 2-1 + | `- 001-auto.sql + |- preinstall + | `- 1 + | |- 001-create_database.pl + | `- 002-create_users_and_permissions.pl + |- schema + | `- 1 + | `- 001-auto.sql + `- up + `- 1-2 + `- 001-auto.sql + +So basically, the code + + $dm->deploy(1) + +on an C database that would simply run +C<$sql_migration_dir/SQLite/schema/1/001-auto.sql>. Next, + + $dm->upgrade_single_step([1,2]) + +would run C<$sql_migration_dir/SQLite/up/1-2/001-auto.sql> followed by +C<$sql_migration_dir/_common/up/1-2/002-generate-customers.pl>. + +Now, a C<.pl> file doesn't have to be in the C<_common> directory, but most of +the time it probably should be, since perl scripts will mostly be database +independent. + +C<_generic> exists for when you for some reason are sure that your SQL is +generic enough to run on all databases. Good luck with that one. + +Note that unlike most steps in the process, C will not run SQL, as +there may not even be an database at preinstall time. It will run perl scripts +just like the other steps in the process, but nothing is passed to them. +Until people have used this more it will remain freeform, but a recommended use +of preinstall is to have it prompt for username and password, and then call the +appropriate C<< CREATE DATABASE >> commands etc. + +=head1 PERL SCRIPTS + +A perl script for this tool is very simple. It merely needs to contain an +anonymous sub that takes a L as it's only argument. +A very basic perl script might look like: + + #!perl + + use strict; + use warnings; + + sub { + my $schema = shift; + + $schema->resultset('Users')->create({ + name => 'root', + password => 'root', + }) + } =attr schema @@ -454,13 +599,13 @@ and generate the DDL. The L that is I used to talk to the database and generate the DDL. This is automatically created with L. -=attr sqltargs +=attr sql_translator_args -#rename +The arguments that get passed to L when it's used. -=attr upgrade_directory +=attr script_directory -The directory (default C<'sql'>) that upgrades are stored in +The directory (default C<'sql'>) that scripts are stored in =attr databases @@ -472,6 +617,11 @@ generate files for Set to true (which is the default) to wrap all upgrades and deploys in a single transaction. +=attr schema_version + +The version the schema on your harddrive is at. Defaults to +C<< $self->schema->schema_version >>. + =method __ddl_consume_with_prefix $dm->__ddl_consume_with_prefix( 'SQLite', [qw( 1.00 1.01 )], 'up' )