fix _read_sql_file and flatten _deploy some
[dbsrgits/DBIx-Class-DeploymentHandler.git] / lib / DBIx / Class / DeploymentHandler / DeployMethod / SQL / Translator.pm
index b3c61ab..6c53b49 100644 (file)
@@ -5,8 +5,11 @@ use Try::Tiny;
 use SQL::Translator;
 require SQL::Translator::Diff;
 require DBIx::Class::Storage;   # loaded for type constraint
+use autodie;
+use File::Path;
 
 with 'DBIx::Class::DeploymentHandler::HandlesDeploy';
+
 use Carp 'carp';
 
 has schema => (
@@ -52,81 +55,105 @@ has _filedata => (
   is  => 'rw',
 );
 
-# these two methods should go away once we switch to
-# DBIx::Migration::Directories
-method _ddl_schema_filename($type, $version, $dir) {
-  my $filename = ref $self->schema;
-  $filename =~ s/::/-/g;
+has txn_wrap => (
+  is => 'ro',
+  isa => 'Bool',
+  default => 1,
+);
 
-  $filename = File::Spec->catfile(
-    $dir, "$filename-schema-$version-$type.sql"
-  );
+method __ddl_consume_with_prefix($type, $versions, $prefix) {
+  my $base_dir = $self->upgrade_directory;
 
-  return $filename;
+  my $main    = File::Spec->catfile( $base_dir, $type                         );
+  my $generic = File::Spec->catfile( $base_dir, '_generic'                    );
+  my $common =  File::Spec->catfile( $base_dir, '_common', $prefix, join q(-), @{$versions} );
+
+  my $dir;
+  if (-d $main) {
+    $dir = File::Spec->catfile($main, $prefix, join q(-), @{$versions})
+  } elsif (-d $generic) {
+    $dir = File::Spec->catfile($main, $prefix, join q(-), @{$versions})
+  } else {
+    die 'PREPARE TO SQL'
+  }
+
+  opendir my($dh), $dir;
+  my %files = map { $_ => "$dir/$_" } grep { /\.sql$/ && -f "$dir/$_" } readdir($dh);
+  closedir $dh;
+
+  if (-d $common) {
+    opendir my($dh), $common;
+    for my $filename (grep { /\.sql$/ && -f "$common/$_" } readdir($dh)) {
+      unless ($files{$filename}) {
+        $files{$filename} = "$common/$_";
+      }
+    }
+    closedir $dh;
+  }
+
+  return [@files{sort keys %files}]
 }
 
-method _ddl_schema_diff_filename($type, $versions, $dir) {
-  my $filename = ref $self->schema;
-  $filename =~ s/::/-/g;
+method _ddl_schema_consume_filenames($type, $version) {
+  $self->__ddl_consume_with_prefix($type, [ $version ], 'schema')
+}
 
-  $filename = File::Spec->catfile(
-    $dir, "$filename-diff-" . join( q(-), @{$versions} ) . "-$type.sql"
+method _ddl_schema_produce_filename($type, $version) {
+  my $base_dir = $self->upgrade_directory;
+  my $dirname = File::Spec->catfile(
+    $base_dir, $type, 'schema', $version
   );
+  File::Path::mkpath($dirname) unless -d $dirname;
 
-  return $filename;
+  return File::Spec->catfile(
+    $dirname, '001-auto.sql'
+  );
 }
 
-method _deployment_statements {
-  my $dir      = $self->upgrade_directory;
-  my $schema   = $self->schema;
-  my $type     = $self->storage->sqlt_type;
-  my $sqltargs = $self->sqltargs;
-  my $version  = $self->schema_version;
-
-  my $filename = $self->_ddl_schema_filename($type, $version, $dir);
-  if(-f $filename) {
-      my $file;
-      open $file, q(<), $filename
-        or carp "Can't open $filename ($!)";
-      my @rows = <$file>;
-      close $file;
-      return join '', @rows;
-  }
+method _ddl_schema_up_consume_filenames($type, $versions) {
+  $self->__ddl_consume_with_prefix($type, $versions, 'up')
+}
+
+method _ddl_schema_down_consume_filenames($type, $versions) {
+  $self->__ddl_consume_with_prefix($type, $versions, 'down')
+}
 
-  # sources needs to be a parser arg, but for simplicty allow at top level
-  # coming in
-  $sqltargs->{parser_args}{sources} = delete $sqltargs->{sources}
-      if exists $sqltargs->{sources};
+method _ddl_schema_up_produce_filename($type, $versions) {
+  my $dir = $self->upgrade_directory;
 
-  my $tr = SQL::Translator->new(
-    producer => "SQL::Translator::Producer::${type}",
-    %$sqltargs,
-    parser => 'SQL::Translator::Parser::DBIx::Class',
-    data => $schema,
+  my $dirname = File::Spec->catfile(
+    $dir, $type, 'up', join( q(-), @{$versions} )
   );
+  File::Path::mkpath($dirname) unless -d $dirname;
 
-  my @ret;
-  my $wa = wantarray;
-  if ($wa) {
-    @ret = $tr->translate;
-  }
-  else {
-    $ret[0] = $tr->translate;
-  }
+  return File::Spec->catfile(
+    $dirname, '001-auto.sql'
+  );
+}
 
-  $schema->throw_exception( 'Unable to produce deployment statements: ' . $tr->error)
-    unless (@ret && defined $ret[0]);
+method _ddl_schema_down_produce_filename($type, $versions, $dir) {
+  my $dirname = File::Spec->catfile(
+    $dir, $type, 'down', join( q(-), @{$versions} )
+  );
+  File::Path::mkpath($dirname) unless -d $dirname;
 
-  return $wa ? @ret : $ret[0];
+  return File::Spec->catfile(
+    $dirname, '001-auto.sql'
+  );
 }
 
 sub _deploy {
   my $self = shift;
   my $storage  = $self->storage;
 
-  my $deploy = sub {
-    my $line = shift;
-    return if(!$line || $line =~ /^--|^BEGIN TRANSACTION|^COMMIT|^\s+$/);
+  my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
+
+  foreach my $line (
+    map @{$self->_read_sql_file($_)}, @{$self->_ddl_schema_consume_filenames(
+      $self->storage->sqlt_type,
+      $self->schema_version
+    )}
+  ) {
     $storage->_query_start($line);
     try {
       # do a dbh_do cycle here, as we need some error checking in
@@ -137,18 +164,9 @@ sub _deploy {
       carp "$_ (running '${line}')"
     }
     $storage->_query_end($line);
-  };
-  my @statements = $self->_deployment_statements();
-  if (@statements > 1) {
-    foreach my $statement (@statements) {
-      $deploy->( $statement );
-    }
-  }
-  elsif (@statements == 1) {
-    foreach my $line ( split(";\n", $statements[0])) {
-      $deploy->( $line );
-    }
   }
+
+  $guard->commit if $self->txn_wrap;
 }
 
 sub prepare_install {
@@ -159,29 +177,23 @@ sub prepare_install {
   my $sqltargs  = $self->sqltargs;
   my $version = $schema->schema_version;
 
-  unless( -d $dir ) {
-    carp "Upgrade directory $dir does not exist, using ./\n";
-    $dir = './';
-  }
-
-
   my $sqlt = SQL::Translator->new({
     add_drop_table          => 1,
     ignore_constraint_names => 1,
     ignore_index_names      => 1,
     parser                  => 'SQL::Translator::Parser::DBIx::Class',
-    %{$sqltargs || {}}
+    %{$sqltargs}
   });
 
   my $sqlt_schema = $sqlt->translate({ data => $schema })
-    or $self->throw_exception ($sqlt->error);
+    or $self->throw_exception($sqlt->error);
 
   foreach my $db (@$databases) {
     $sqlt->reset;
     $sqlt->{schema} = $sqlt_schema;
     $sqlt->producer($db);
 
-    my $filename = $self->_ddl_schema_filename($db, $version, $dir);
+    my $filename = $self->_ddl_schema_produce_filename($db, $version, $dir);
     if (-e $filename ) {
       carp "Overwriting existing DDL file - $filename";
       unlink $filename;
@@ -202,7 +214,7 @@ sub prepare_install {
   }
 }
 
-sub prepare_update {
+sub prepare_upgrade {
   my ($self, $from_version, $to_version, $version_set) = @_;
 
   $from_version ||= $self->db_version;
@@ -212,16 +224,30 @@ sub prepare_update {
   # one would want to explicitly set $version_set to
   # [$to_version]
   $version_set  ||= [$from_version, $to_version];
+
+  $self->_prepare_changegrade($from_version, $to_version, $version_set, 'up');
+}
+
+sub prepare_downgrade {
+  my ($self, $from_version, $to_version, $version_set) = @_;
+
+  $from_version ||= $self->db_version;
+  $to_version   ||= $self->schema_version;
+
+  # for updates prepared automatically (rob's stuff)
+  # one would want to explicitly set $version_set to
+  # [$to_version]
+  $version_set  ||= [$from_version, $to_version];
+
+  $self->_prepare_changegrade($from_version, $to_version, $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;
 
-  unless( -d $dir ) {
-    carp "Upgrade directory $dir does not exist, using ./\n";
-    $dir = "./";
-  }
-
   my $schema_version = $schema->schema_version;
 
   $sqltargs = {
@@ -242,15 +268,15 @@ sub prepare_update {
     $sqlt->{schema} = $sqlt_schema;
     $sqlt->producer($db);
 
-    my $prefilename = $self->_ddl_schema_filename($db, $from_version, $dir);
+    my $prefilename = $self->_ddl_schema_produce_filename($db, $from_version, $dir);
     unless(-e $prefilename) {
       carp("No previous schema file found ($prefilename)");
       next;
     }
-
-    my $diff_file = $self->_ddl_schema_diff_filename($db, $version_set, $dir );
+    my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
+    my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
     if(-e $diff_file) {
-      carp("Overwriting existing diff file - $diff_file");
+      carp("Overwriting existing $direction-diff file - $diff_file");
       unlink $diff_file;
     }
 
@@ -289,7 +315,7 @@ sub prepare_update {
       $t->parser( $db ) # could this really throw an exception?
         or $self->throw_exception ($t->error);
 
-      my $filename = $self->_ddl_schema_filename($db, $to_version, $dir);
+      my $filename = $self->_ddl_schema_produce_filename($db, $to_version, $dir);
       my $out = $t->translate( $filename )
         or $self->throw_exception ($t->error);
 
@@ -317,37 +343,65 @@ sub prepare_update {
 method _read_sql_file($file) {
   return unless $file;
 
-  open my $fh, '<', $file or carp("Can't open upgrade file, $file ($!)");
-  my @data = split /\n/, join '', <$fh>;
+  open my $fh, '<', $file or carp("Can't open sql file, $file ($!)");
+  my @data = split /;\n/, join '', <$fh>;
   close $fh;
 
   @data = grep {
-    $_ &&
-    !/^--/ &&
-    !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/m
-  } split /;/,
-    join '', @data;
+    $_ && # remove blank lines
+    !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/ # strip txn's
+  } map {
+    s/^\s+//; s/\s+$//; # trim whitespace
+    join '', grep { !/^--/ } split /\n/ # remove comments
+  } @data;
 
   return \@data;
 }
 
-sub _upgrade_single_step {
+# these are exactly the same for now
+sub _downgrade_single_step {
   my $self = shift;
   my @version_set = @{ shift @_ };
-  my $upgrade_file = $self->_ddl_schema_diff_filename(
+  my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
     $self->storage->sqlt_type,
     \@version_set,
-    $self->upgrade_directory,
-  );
+  )};
+
+  for my $upgrade_file (@upgrade_files) {
+    unless (-f $upgrade_file) {
+      # croak?
+      carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
+      return;
+    }
 
-  unless (-f $upgrade_file) {
-    # croak?
-    carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
-    return;
+    $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
+
+    my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
+    $self->_do_upgrade;
+    $guard->commit if $self->txn_wrap;
   }
+}
+
+sub _upgrade_single_step {
+  my $self = shift;
+  my @version_set = @{ shift @_ };
+  my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
+    $self->storage->sqlt_type,
+    \@version_set,
+  )};
 
-  $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
-  $self->schema->txn_do(sub { $self->_do_upgrade });
+  for my $upgrade_file (@upgrade_files) {
+    unless (-f $upgrade_file) {
+      # croak?
+      carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
+      return;
+    }
+
+    $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
+    my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
+    $self->_do_upgrade;
+    $guard->commit if $self->txn_wrap;
+  }
 }
 
 method _do_upgrade { $self->_run_upgrade(qr/.*?/) }
@@ -368,8 +422,6 @@ method _apply_statement($statement) {
   $self->storage->dbh->do($_) or carp "SQL was: $_"
 }
 
-__PACKAGE__->meta->make_immutable;
-
 1;
 
 __END__