NAME

DBIO::MySQL::Deploy - Deploy and upgrade MySQL/MariaDB schemas via test-deploy-and-compare

VERSION

version 0.900004

DESCRIPTION

DBIO::MySQL::Deploy orchestrates the deployment and upgrade of MySQL/MariaDB schemas using a test-deploy-and-compare strategy: it deploys the desired schema into a freshly created temporary CREATE DATABASE database, introspects it, and diffs the result against the live database. See DBIO::Deploy::Base for the shared install/apply/upgrade flow; see DBIO::Deploy::Base::TempDatabase for the temp-database orchestration this driver inherits.

my $deploy = DBIO::MySQL::Deploy->new(
    schema => MyApp::DB->connect($dsn, $user, $pass),
);

$deploy->install;     # fresh install
$deploy->upgrade;     # diff + apply

Transactional DDL -- the engine has none, on any version

DBIO::MySQL::Storage declares _use_transactional_ddl(0), so "_execute_ddl" in DBIO::Deploy::Base runs the statements one at a time with AutoCommit on and core emits its one-time "non-transactional DDL" notice. That notice is accurate here: an apply that fails half way leaves the earlier statements applied. Re-running upgrade is the recovery, and it is safe, because the diff is recomputed against live state and the second run picks up only what is left.

The declaration is an engine fact, not a conservative guess. Every statement this driver emits -- CREATE TABLE, CREATE VIEW, ALTER TABLE, CREATE INDEX, DROP INDEX, DROP TABLE -- appears on both products' implicit-commit list (MySQL Reference Manual 15.3.3; MariaDB, SQL statements Causing an Implicit Commit), which means the transaction is committed before the statement runs, whether or not the statement then succeeds.

Atomic DDL is not transactional DDL, and the two are easy to confuse. MySQL 8.0 and MariaDB 10.6.1 both made a single DDL statement crash-safe -- no table left without its dictionary entry, no leftover shadow file. Neither put DDL under transaction control, and MySQL's manual says so in the same section that introduces the feature. Raising this capability to 1 on the strength of a server version would not make a multi-statement upgrade atomic; it would only bracket the loop in $storage->txn_do, where the BEGIN is committed away by the next CREATE TABLE and the ROLLBACK takes back nothing -- while silencing the notice that says so. The full citation trail sits at the declaration in DBIO::MySQL::Storage, and t/58-deploy-transactional-ddl.t pins it.

Only the live connection is affected either way: the inherited "_build_target_model" in DBIO::Deploy::Base::TempDatabase deploys into a throwaway database over a handle it opened itself, and _execute_ddl never brackets a handle the storage does not own, whatever the capability says. That temp database is dropped whole on failure, which is its unit of recovery.

ATTRIBUTES

add_drop_table

DBIO::MySQL::Deploy->new(schema => $schema, add_drop_table => 1)->install;

When true, "install" in DBIO::Deploy::Base takes away what the install creates before it creates it: every view (DROP VIEW IF EXISTS, a reader before the view it reads) and every table ("drop_ddl" in DBIO::MySQL::DDL), so a second install over a populated database runs through and leaves it as new. The foreign key checks of the connection are off for the drops and on again after, whether they ran through or not. Default false: an install over an existing table fails, as it always did. "deploy" in DBIO::Schema does not hand its add_drop_table option to the Deploy class, so the option is set here, on the object.

temp_db_prefix

Prefix for temporary databases created during diff. Defaults to _dbio_tmp_. The full name includes the PID and current timestamp. Inherited from DBIO::Deploy::Base::TempDatabase.

METHODS

_check_before_apply

$self->_check_before_apply($diff);

The driver's checks of "apply" in DBIO::Deploy::Base, run for a released diff before its first statement: "_refuse_missing_databases" (a table of a database the server does not have) and "_refuse_unfillable" (the rows the live tables hold), both against the live connection. apply and upgrade are DBIO::Deploy::Base's as they are: the guard for destructive and lossy operations (allow_destructive), dry_run, the extension protocol, and the statements run through its _execute_diff. dry_run checks nothing and asks the server nothing.

_refuse_unfillable

$self->_refuse_unfillable($diff, $dbh);

Looks at the rows of the live tables before the first statement runs, and refuses -- a DBIO::Exception naming table and column -- what the diff cannot do without inventing data:

  • a NOT NULL column without a default that the diff adds to a table that holds rows. Left to the server it succeeds and gives every row the implicit default of the type ('', 0, 0000-00-00), a value nobody declared;

  • a column that becomes NOT NULL in a table that has a NULL in it. A strict server stops the MODIFY COLUMN half way through the diff (1138, Invalid use of NULL value); without a strict sql_mode it turns every NULL into that implicit default, silently.

An empty table, and a table with no NULL in the column, pass. An AUTO_INCREMENT column is filled by the server and passes. The ops before the one refused are not applied: the check runs before the first statement.

_refuse_missing_databases

$self->_refuse_missing_databases($dbh);

A source whose table is database.table is made in that database ("qualified_tables" in DBIO::MySQL::DDL). The driver creates no database: one the server does not have ends the install, and the diff and the upgrade, before their first statement, with a DBIO::Exception naming the table and the database.

_other_databases

my @databases = $self->_other_databases;

The databases, sorted and once each, that the tables named database.table of the schema live in ("qualified_tables" in DBIO::MySQL::DDL).

_introspect_current

The model of the live database, and of each other database the schema has a table in: a database the server does not have ends the diff here, before anything is made ("_refuse_missing_databases"); one it has is introspected over a connection of its own, the way the temporary database is reached, and its model keeps the tables the schema names there and nothing else, so a table of that database that is not the schema's is never dropped. The models are in other_databases ("Tables of other databases" in DBIO::MySQL::Diff).

_build_target_model

The target model, and for each other database the schema has a table in a model of its tables there: installed into a temporary database of its own under the names they have in the database, and introspected, as the current database's tables are. A table of another database that cannot be compared that way ends the diff with the exception of the failing statement; nothing is left out.

AUTHOR

DBIO & DBIx::Class Authors

COPYRIGHT AND LICENSE

Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.