1 package DBIx::Class::DeploymentHandler;
4 use Method::Signatures::Simple;
5 require DBIx::Class::Schema; # loaded for type constraint
6 require DBIx::Class::Storage; # loaded for type constraint
10 isa => 'DBIx::Class::Schema',
13 handles => [qw{schema_version}],
16 has upgrade_directory => (
23 has backup_directory => (
29 isa => 'DBIx::Class::Storage',
44 has do_diff_on_init => (
50 method _build_storage {
51 return $self->schema->storage;
54 method install($new_version) {
55 # must be called on a fresh database
56 if ($self->get_db_version) {
57 carp 'Install not possible as versions table already exists in database';
60 # default to current version if none passed
61 $new_version ||= $self->schema_version();
64 # create versions table and version row
65 $self->{vschema}->deploy;
66 $self->_set_db_version({ version => $new_version });
71 $self->next::method(@_);
75 sub create_upgrade_path {
76 ## override this method
79 sub ordered_schema_versions {
80 ## override this method
84 my $db_version = $self->get_db_version();
87 unless ($db_version) {
88 carp 'Upgrade not possible as database is unversioned. Please call install first.';
92 # db and schema at same version. do nothing
93 if ( $db_version eq $self->schema_version ) {
94 carp "Upgrade not necessary\n";
98 my @version_list = $self->ordered_schema_versions;
100 # if nothing returned then we preload with min/max
101 @version_list = ( $db_version, $self->schema_version )
102 unless ( scalar(@version_list) );
104 # catch the case of someone returning an arrayref
105 @version_list = @{ $version_list[0] }
106 if ( ref( $version_list[0] ) eq 'ARRAY' );
108 # remove all versions in list above the required version
109 while ( scalar(@version_list)
110 && ( $version_list[-1] ne $self->schema_version ) )
115 # remove all versions in list below the current version
116 while ( scalar(@version_list) && ( $version_list[0] ne $db_version ) ) {
120 # check we have an appropriate list of versions
121 if ( scalar(@version_list) < 2 ) {
126 while ( scalar(@version_list) >= 2 ) {
127 $self->upgrade_single_step( $version_list[0], $version_list[1] );
132 method upgrade_single_step($db_version, $target_version) {
133 # db and schema at same version. do nothing
134 if ($db_version eq $target_version) {
135 carp "Upgrade not necessary\n";
139 # strangely the first time this is called can
140 # differ to subsequent times. so we call it
143 $self->storage->sqlt_type;
145 my $upgrade_file = $self->ddl_filename(
146 $self->storage->sqlt_type,
148 $self->upgrade_directory,
152 $self->create_upgrade_path({ upgrade_file => $upgrade_file });
154 unless (-f $upgrade_file) {
155 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
159 carp "DB version ($db_version) is lower than the schema version (".$self->schema_version."). Attempting upgrade.\n";
161 # backup if necessary then apply upgrade
162 $self->_filedata($self->_read_sql_file($upgrade_file));
163 $self->backup() if($self->do_backup);
164 $self->txn_do(sub { $self->do_upgrade() });
166 # set row in dbix_class_schema_versions table
167 $self->_set_db_version({version => $target_version});
171 # just run all the commands (including inserts) in order
172 $self->run_upgrade(qr/.*?/);
175 method run_upgrade($stm) {
176 return unless ($self->_filedata);
177 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
178 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
181 $self->storage->debugobj->query_start($_) if $self->storage->debug;
182 $self->apply_statement($_);
183 $self->storage->debugobj->query_end($_) if $self->storage->debug;
189 method apply_statement($statement) {
190 $self->storage->dbh->do($_) or carp "SQL was: $_";
193 method get_db_version {
194 my $vtable = $self->schema->resultset('VersionResult');
195 my $version = $vtable->search(undef, {
196 order_by => { -desc => 'installed' },
198 })->get_column('version')->next || 0;
203 ## Make each ::DBI::Foo do this
204 $self->storage->backup($self->backup_directory());
208 $self->next::method(@_);
209 $self->_on_connect($_[3]);
213 method _on_connect($args) {
216 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
217 my $vtable = $self->{vschema}->resultset('Table');
219 # useful when connecting from scripts etc
220 return if ($args->{ignore_version} || ($ENV{DBIC_NO_VERSION_CHECK} && !exists $args->{ignore_version}));
222 # check for legacy versions table and move to new if exists
223 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
224 unless ($self->_source_exists($vtable)) {
225 my $vtable_compat = $vschema_compat->resultset('TableCompat');
226 if ($self->_source_exists($vtable_compat)) {
227 $self->{vschema}->deploy;
228 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
229 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
233 my $pversion = $self->get_db_version();
235 if($pversion eq $self->schema_version)
237 # carp "This version is already installed\n";
243 carp "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
247 carp "Versions out of sync. This is " . $self->schema_version .
248 ", your database contains version $pversion, please call upgrade on your Schema.\n";
251 sub _create_db_to_schema_diff {
254 my %driver_to_db_map = (
258 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}{Name}};
260 print "Sorry, this is an unsupported DB\n";
264 $self->throw_exception($self->storage->_sqlt_version_error)
265 unless $self->storage->_sqlt_version_ok;
267 my $db_tr = SQL::Translator->new({
270 parser_args => { dbh => $self->storage->dbh },
274 my $dbic_tr = SQL::Translator->new({
275 parser => 'SQL::Translator::Parser::DBIx::Class',
280 $db_tr->schema->name('db_schema');
281 $dbic_tr->schema->name('dbic_schema');
283 # is this really necessary?
284 foreach my $tr ($db_tr, $dbic_tr) {
285 my $data = $tr->data;
286 $tr->parser->($tr, $$data);
289 my $diff = SQL::Translator::Diff::schema_diff(
291 $dbic_tr->schema, $db, {
292 ignore_constraint_names => 1,
293 ignore_index_names => 1,
298 my $filename = $self->ddl_filename(
300 $self->schema_version,
301 $self->upgrade_directory,
305 open my $file, '>', $filename
306 or $self->throw_exception("Can't open $filename for writing ($!)");
310 carp "WARNING: There may be differences between your DB and your DBIC schema.\n" .
311 "Please review and if necessary run the SQL in $filename to sync your DB.\n";
314 method _read_sql_file($file) {
317 open my $fh, '<', $file or carp("Can't open upgrade file, $file ($!)");
318 my @data = split /\n/, join '', <$fh>;
324 !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/m
331 method _source_exists($rs) {
333 $rs->search({ 1, 0 })->count;
335 return 0 if $@ || !defined $c;