| 0 | 1 use strict; | 
|  | 2 package Deployment::Batch::CDBIUpdate; | 
|  | 3 use Common; | 
|  | 4 use base qw(Deployment::Batch::Generic); | 
|  | 5 | 
|  | 6 use DBI; | 
|  | 7 use Schema::DataSource; | 
|  | 8 use Schema::DataSource::CDBIBuilder; | 
|  | 9 | 
|  | 10 | 
|  | 11 BEGIN { | 
|  | 12     DeclareProperty DataSchemaFile => ACCESS_READ; | 
|  | 13     DeclareProperty DataSourceDir => ACCESS_READ; | 
|  | 14     DeclareProperty DSNamespace => ACCESS_READ; | 
|  | 15     DeclareProperty DBConnection => ACCESS_READ; | 
|  | 16     DeclareProperty DBTraitsClass => ACCESS_READ; | 
|  | 17     DeclareProperty SchemaPrev => ACCESS_READ; | 
|  | 18 } | 
|  | 19 | 
|  | 20 sub CTOR { | 
|  | 21     my ($this,%args) = @_; | 
|  | 22 | 
|  | 23     $this->{$DataSchemaFile} = $args{'Source'} or die new Exception('A data shema file is required'); | 
|  | 24     $this->{$DataSourceDir} = $args{'Output'} or die new Exception('A directory for a data source is required'); | 
|  | 25     $this->{$DSNamespace} = $args{'Namespace'} || 'DataSource'; | 
|  | 26     $this->{$DBTraitsClass} = $args{'DBTraits'} or die new Exception('A DBTraitsClass is required'); | 
|  | 27 | 
|  | 28     (my $modname = $args{'DBTraits'}.'.pm') =~ s/::/\//g; | 
|  | 29     $this->Log("Loading DBTraits '$modname'"); | 
|  | 30     require $modname; | 
|  | 31 } | 
|  | 32 | 
|  | 33 sub Run { | 
|  | 34     my ($this) = @_; | 
|  | 35 | 
|  | 36     $this->{$DBConnection} = $this->Context->{Connection}; | 
|  | 37 | 
|  | 38     my $prefix = $this->{$DSNamespace}.'::'; | 
|  | 39 | 
|  | 40     my $schemaDS = new Schema::DataSource(DataSourceBuilder => new Schema::DataSource::CDBIBuilder); | 
|  | 41     $schemaDS->BuildSchema($this->{$DataSchemaFile}); | 
|  | 42 | 
|  | 43     my $schemaDB = $schemaDS->DataSourceBuilder->BuildDBSchema(); | 
|  | 44     (my $fname = $this->{$DataSourceDir}.$this->{$DSNamespace}.'.pm') =~ s/::/\//g; | 
|  | 45 | 
|  | 46     # we are in the immediate mode, so the file will be backupped immediatelly; | 
|  | 47     $this->Log("Backup $fname"); | 
|  | 48     Deployment::Batch->Backup( File => $fname ); | 
|  | 49 | 
|  | 50     $this->Log("Write the datasource '$this->{$DSNamespace}' to '$this->{$DataSourceDir}'"); | 
|  | 51     $schemaDS->DataSourceBuilder->WriteModules($fname,$prefix); | 
|  | 52 | 
|  | 53     if ($this->{$DBConnection}) { | 
|  | 54         $this->Log("Update the database '$this->{$DBConnection}[0]'"); | 
|  | 55 | 
|  | 56         $this->{$SchemaPrev} = $this->UpdateDBToSchema($schemaDB); | 
|  | 57 | 
|  | 58     } | 
|  | 59     $schemaDB->Dispose; | 
|  | 60 } | 
|  | 61 | 
|  | 62 sub Rollback { | 
|  | 63     my ($this) = @_; | 
|  | 64 | 
|  | 65     if ($this->{$SchemaPrev}) { | 
|  | 66         $this->Log("Rallback the DB schema"); | 
|  | 67         $this->UpdateDBToSchema($this->{$SchemaPrev})->Dispose; | 
|  | 68         $this->{$SchemaPrev}->Dispose; | 
|  | 69         delete $this->{$SchemaPrev}; | 
|  | 70     } | 
|  | 71 | 
|  | 72 } | 
|  | 73 | 
|  | 74 sub UpdateDBToSchema { | 
|  | 75     my ($this,$schemaDB) = @_; | 
|  | 76     my $dbh = DBI->connect(@{$this->{$DBConnection}}) or die new Exception('Failed to connect to the database',@{$this->{$DBConnection}}); | 
|  | 77     my $SchemaSource; | 
|  | 78 | 
|  | 79     if (UNIVERSAL::can($this->{$DBTraitsClass},'GetMetaTable')) { | 
|  | 80         $SchemaSource = new Deployment::CDBI::SQLSchemeSource (MetaTable => $this->{$DBTraitsClass}->GetMetaTable($dbh)); | 
|  | 81     } else { | 
|  | 82         die new Exception("Can't get a meta table",$this->{$DBTraitsClass}); | 
|  | 83     } | 
|  | 84 | 
|  | 85     my $schemaDBOld = $SchemaSource->ReadSchema($schemaDB->Name); | 
|  | 86 | 
|  | 87     my $updater = $this->{$DBTraitsClass}->new(SrcSchema => $schemaDBOld, DstSchema => $schemaDB); | 
|  | 88     $updater->UpdateSchema(); | 
|  | 89 | 
|  | 90     $dbh->do($_) or die new Exception('Failed to execute the sql statement', $_) foreach $updater->Handler->Sql; | 
|  | 91 | 
|  | 92     $SchemaSource->SaveSchema($schemaDB); | 
|  | 93     return $schemaDBOld; | 
|  | 94 } | 
|  | 95 | 
|  | 96 sub DESTROY { | 
|  | 97     my $this = shift; | 
|  | 98 | 
|  | 99     $this->{$SchemaPrev}->Dispose if $this->{$SchemaPrev}; | 
|  | 100 } | 
|  | 101 | 
|  | 102 package Deployment::CDBI::SQLSchemeSource; | 
|  | 103 use Common; | 
|  | 104 use Data::Dumper; | 
|  | 105 use MIME::Base64; | 
|  | 106 use Storable qw(nstore_fd fd_retrieve); | 
|  | 107 our @ISA = qw(Object); | 
|  | 108 | 
|  | 109 BEGIN { | 
|  | 110     DeclareProperty MetaTable => ACCESS_NONE; | 
|  | 111 } | 
|  | 112 | 
|  | 113 sub ReadSchema { | 
|  | 114     my ($this,$name) = @_; | 
|  | 115 | 
|  | 116     my $schema = decode_base64($this->{$MetaTable}->ReadProperty("db_schema_$name")); | 
|  | 117     if ($schema) { | 
|  | 118         open my $hvar,"<",\$schema or die new Exception("Failed to create a handle to the variable"); | 
|  | 119         return fd_retrieve($hvar); | 
|  | 120     } else { | 
|  | 121         return new Schema::DB(Name => $name, Version => 0); | 
|  | 122     } | 
|  | 123 } | 
|  | 124 | 
|  | 125 sub SaveSchema { | 
|  | 126     my ($this,$schema) = @_; | 
|  | 127 | 
|  | 128     my $name = $schema->Name; | 
|  | 129 | 
|  | 130     my $data = ""; | 
|  | 131     { | 
|  | 132         open my $hvar,">",\$data or die new Exception("Failed to create a handle to the variable"); | 
|  | 133         nstore_fd($schema,$hvar); | 
|  | 134     } | 
|  | 135 | 
|  | 136     $this->{$MetaTable}->SetProperty("db_schema_$name",encode_base64($data)); | 
|  | 137 } | 
|  | 138 | 
|  | 139 1; |