NAME

DBIO::Deploy::Base - Base class for DBIO driver deploy orchestrators

VERSION

version 0.900001

DESCRIPTION

Base class for the per-driver Deploy orchestrators (DBIO::PostgreSQL::Deploy, DBIO::MySQL::Deploy, DBIO::DuckDB::Deploy, DBIO::SQLite::Deploy). Those classes implement the same test-deploy-and-compare strategy and their install/apply/upgrade methods were byte-near-identical; only the way diff obtains the target model (deploy the desired schema into a throwaway database and introspect it) is genuinely engine-specific.

This base hosts the shared orchestration:

  • "install" -- generate install DDL and run it against the live db.

  • "apply" -- run a diff's SQL against the live db.

  • "upgrade" -- "diff" then "apply".

  • "diff" -- introspect the live db (source), build the target model, and hand both to the driver's diff class.

against a handful of hooks. A subclass must provide the three class-name hooks ("_ddl_class", "_introspect_class", "_diff_class") and "_build_target_model" -- the "make a throwaway, deploy the desired schema, introspect it" step that is the real engine seam. Drivers that use a temporary database (PostgreSQL, MySQL) get a ready-made "_build_target_model" by subclassing DBIO::Deploy::Base::TempDatabase instead; in-memory drivers (DuckDB, SQLite) override "_build_target_model" directly with a :memory: connection.

ATTRIBUTES

schema

A connected DBIO::Schema instance using the driver's component. Required.

METHODS

new

my $deploy = $class->new(schema => $connected_schema, %extra);

Blesses the argument hash. schema is required.

_ddl_class

Class name whose install_ddl($schema) returns the install DDL. Abstract.

_introspect_class

Class name whose new(dbh => $dbh)->model returns an introspected model. Abstract.

_diff_class

Class name whose new(source => $m, target => $m) returns a diff object (responding to has_changes and as_sql). Abstract.

_new_introspect

my $intro = $self->_new_introspect($dbh);

Factory for an introspector over $dbh. Default: _introspect_class->new(dbh => $dbh). Override to pass extra construction args (e.g. PostgreSQL's schema_filter).

_introspect_current

The introspected model of the live (source) database. Default: $self->_new_introspect($self->_dbh)->model.

_install_ddl

The install DDL string for the connected schema. Default: _ddl_class->install_ddl($self->schema).

_execute_ddl

$self->_execute_ddl($dbh, $sql);

Splits $sql into statements ("_split_statements" in DBIO::SQL::Util) and runs each via $dbh->do, skipping comment-only statements.

When the storage reports _use_transactional_ddl truthy, the loop is wrapped in $storage->txn_do so a failure on any statement rolls the whole batch back. When the storage reports 0 (the default for engines whose DDL forces an implicit COMMIT -- MySQL pre-8.0, Oracle, DB2, Sybase, Informix -- or whose rebuild path depends on AutoCommit=on, e.g. SQLite), the loop runs statement-at-a-time as before and a carp is emitted naming this engine class so operators see that a partial-failure recovery depends on per-driver bookkeeping (e.g. the __VERSION row gate in DBIO::DeploymentHandler).

_build_target_model

The desired-state model: deploy the install DDL into a throwaway database and introspect it. Abstract here -- provided by DBIO::Deploy::Base::TempDatabase for temp-database drivers, or overridden directly by in-memory drivers.

install

$deploy->install;

Generates the install DDL and executes it against the connected database. Suitable for a fresh install on an empty database. Returns true.

diff

my $diff = $deploy->diff;

Introspects the live database (source), builds the target model via "_build_target_model", and returns _diff_class->new(source => ..., target => ...).

apply

$deploy->apply($diff);

Executes each statement of $diff->as_sql against the connected database. No-op (returns false) when $diff->has_changes is false.

upgrade

my $diff = $deploy->upgrade;

Convenience: "diff" then "apply". Returns the diff object if changes were applied, or undef if the database was already up to date.

CONTRACT VERSION

This class exposes an independent compatibility version, distinct from $VERSION (the dist version injected by Dist::Zilla's VersionFromMainModule):

my $v = $class->contract_version;

$CONTRACT_VERSION bumps when the deploy orchestrator's public interface (install, apply, upgrade, diff) or the hook contract (_ddl_class, _introspect_class, _diff_class, _build_target_model) changes. The dist $VERSION bumps on every release, but two core releases at the same contract version remain wire-compatible. Out-of-tree drivers should record the contract version they were last tested against and compare it against core's at load time, warning (or strict-failing under DBIO_STRICT_CONTRACT) when the shapes have drifted. See docs/adr/ for the contract-version policy.

TRANSACTIONAL DDL

Whether the loop in "_execute_ddl" is wrapped in $storage->txn_do is governed by the transactional_ddl capability on the storage (DBIO::Storage::DBI::Capabilities):

  • Engines where DDL is transactional: PostgreSQL, Firebird, the newer transactional DDL mode in MariaDB 10.6+ / MySQL 8.0+. The driver sets _use_transactional_ddl(1) and the whole multi-statement DDL runs as one atomic unit -- a failure on statement 7 of 12 rolls back the preceding six.

  • Engines that force an implicit COMMIT on DDL: MySQL (pre 8.0 unless explicit transactional DDL is on), Oracle, DB2, Sybase, Informix. On these engines wrapping $dbh->do() in txn_do is a no-op as far as DDL is concerned, and the loop runs statement-at-a-time as before. A carp is emitted at the first "_execute_ddl" call naming this engine class so operators see that a partial-failure recovery depends on the driver's own bookkeeping (e.g. version-row gates).

  • Engines whose rebuild path depends on AutoCommit=on: SQLite. We deliberately do not wrap in txn_do for SQLite even when the storage reports transactional DDL: a blanket txn_do wrap would regress SQLite's in-place rebuilds. Drivers that need transactional DDL on a non-transactional engine (e.g. PostgreSQL on the wire) opt in via DBIO::Storage::DBI::Capabilities's _use_transactional_ddl(1).

Recovery on the non-transactional set is engine-specific. MySQL rolls back DDL on most error paths (CREATE TABLE / DROP TABLE / RENAME are atomic per-statement at the storage level) but multi-statement DDL bundles can still leave the schema half-applied. DBIO::DeploymentHandler relies on the __VERSION row as a forward-progress gate: a partially applied upgrade can be re-run safely because the diff re-computes against live state, and the next apply picks up the remainder.

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.