NAME

DBIO::Manual::Heritage - Everything that changed from DBIx::Class to DBIO

VERSION

version 0.900000

OVERVIEW

DBIO is a fork of DBIx::Class. It retains the relational-mapping model and most of the API surface, but replaces, extends, and integrates a significant number of components. This document is the definitive reference for everyone migrating from DBIx::Class, or anyone who needs to understand what DBIO is relative to its ancestor.

Naming

DBIO stands for DBI Objects. The namespace DBIO:: replaces DBIx::Class:: throughout. Every module, every test, every tool has been renamed accordingly.

DBIO is a clean break - there is no runtime DBIx::Class compatibility shim. Existing code is migrated by mechanical rename (DBIx::Class::* to DBIO::*) plus the targeted API updates listed in DBIO::Manual::Migration.

FUNDAMENTAL ARCHITECTURE CHANGES

DBIO::Base - the new meta-infrastructure base

DBIx::Class distributed its meta-level machinery across several internal packages. DBIO consolidates it into DBIO::Base, which every internal class uses as its root:

use mro 'c3' - MRO forced to C3 for the entire inheritance tree
DBIO::Componentised - the load_components() machinery
Class::Accessor::Grouped - mk_group_accessors()
Perl subroutine attributes via MODIFY_CODE_ATTRIBUTES
_skip_namespace_frames for clean stack traces

Do not use DBIO::Base directly in application code. It is the machinery that everything else stands on. For your Result classes use DBIO::Core; for ResultSet classes use DBIO::ResultSet; for schema classes use DBIO::Schema.

SQL::Abstract replaces SQL::Abstract::Classic

DBIx::Class depended on SQL::Abstract::Classic, an older fork. DBIO uses the canonical SQL::Abstract (>= 1.99) instead. The query API is compatible; the internal extensions required small adjustments in the SQLMaker layer.

apply_limit replaces limit_dialect

DBIx::Class used a sql_limit_dialect string and an emulate_limit() dispatch table inside the storage layer. DBIO removes both. Each driver's SQLMaker subclass provides an apply_limit($sql, $rows, $offset) method instead. The default implementation emits LIMIT ? OFFSET ?.

If you had custom emulate_limit() overrides, override apply_limit on your SQLMaker subclass.

Driver distributions split out of core

All database-specific storage classes have been removed from the DBIO core distribution and moved into dedicated CPAN distributions:

DBIO-PostgreSQL    DBIO::PostgreSQL    (most advanced: pg_catalog introspect,
                                        enums, schemas, RLS, indexes)
DBIO-MySQL         DBIO::MySQL
DBIO-SQLite        DBIO::SQLite
DBIO-DuckDB        DBIO::DuckDB        (embedded analytical; UUID, Arrow,
                                        Appender, quack RPC)
DBIO-DB2           DBIO::DB2
DBIO-Firebird      DBIO::Firebird
DBIO-Informix      DBIO::Informix
DBIO-MSSQL         DBIO::MSSQL
DBIO-Oracle        DBIO::Oracle
DBIO-Sybase        DBIO::Sybase

Install only the driver you need. Every driver ships its own native introspection, diff, and deploy modules (the test-and-compare strategy) and does not require SQL::Translator.

Two drivers also have non-blocking async variants whose queries return Future objects, and there is a GraphQL projection layer:

DBIO-PostgreSQL-Async   DBIO::PostgreSQL::Async   (libpq via EV::Pg)
DBIO-MySQL-Async        DBIO::MySQL::Async        (MariaDB via EV::MariaDB)
DBIO-GraphQL            DBIO::GraphQL             (Result classes -> GraphQL)

Replicated storage is now in core

DBIx::Class::Replicated was an external distribution. DBIO::Replicated ships in the core distribution. Load it as a Schema component; it forces +DBIO::Replicated::Storage and coordinates a master backend plus optional replicants:

package MyApp::Schema;
use base 'DBIO::Schema';
__PACKAGE__->load_components('DBIO::Replicated');

my $schema = MyApp::Schema->connect($dsn, $user, $pass, {
    balancer_type => 'DBIO::Replicated::Balancer::First',
});
$schema->storage->connect_replicants([ $replica_dsn, $user, $pass ]);

Writes, transactions and deploy go through the master; reads are delegated to the balancer (default DBIO::Replicated::Balancer::First). The internals live in DBIO::Replicated::Pool, DBIO::Replicated::Backend, and DBIO::Replicated::Balancer.

SQL::Translator is optional

Every driver ships its own native Deploy, Introspect, and Diff modules. DBIO::Schema->deploy() checks the active storage class for a dbio_deploy_class() method; if present it routes to the native class, which deploys by introspecting both the live database and the desired schema and diffing the two models (test-and-compare). If a storage declares no native class, deploy() does not silently translate the schema for you: it falls through to deployment_statements(), which reads a pre-existing DDL file and throws if none is present. Core's deploy path never calls SQL::Translator, so SQL::Translator is an optional dependency rather than a required one.

RESULT CLASS STYLES

DBIx::Class had one way to write a Result class: the explicit __PACKAGE__->method() style. DBIO keeps that (called Vanilla) and adds two sugar layers.

Vanilla (classic, unchanged API)

package MyApp::Schema::Result::Artist;
use base 'DBIO::Core';
__PACKAGE__->table('artist');
__PACKAGE__->add_columns(
    artistid => { data_type => 'integer', is_auto_increment => 1 },
    name     => { data_type => 'varchar', size => 100 },
);
__PACKAGE__->set_primary_key('artistid');
__PACKAGE__->has_many(cds => 'MyApp::Schema::Result::CD', 'artistid');
1;

See DBIO::Core and DBIO::Manual::ResultClass.

Candy (import-sugar, method renaming)

DBIO::Candy removes the __PACKAGE__-> boilerplate and exports short aliases. It also infers the table name from the package name.

package MyApp::Schema::Result::Artist;
use DBIO::Candy;
column artistid => { data_type => 'integer', is_auto_increment => 1 };
column name     => { data_type => 'varchar', size => 100 };
primary_key 'artistid';
has_many cds => 'MyApp::Schema::Result::CD', 'artistid';
1;

Key aliases exported by Candy:

column             -> add_columns
primary_key        -> set_primary_key
unique_constraint  -> add_unique_constraint
relationship       -> add_relationship

Candy is derived from DBIx::Class::Candy, which has been integrated into the core distribution.

Cake (DDL-like DSL - most concise)

DBIO::Cake reads like schema DDL. Type functions (integer, varchar, serial, uuid, json, jsonb, text, boolean, ...) replace the verbose column-info hashref.

package MyApp::Schema::Result::Artist;
use DBIO::Cake;
col artistid => serial, auto_inc;
col name     => varchar(100);
primary_key 'artistid';
has_many cds => 'MyApp::Schema::Result::CD', 'artistid';
1;

Cake modifiers:

null        auto_inc     fk($target)
default     unsigned     on_create    on_update

Driver shortcuts activate driver-specific column types and components in one import flag:

use DBIO::Cake -Pg;       # PostgreSQL defaults (uuid, jsonb, ...)
use DBIO::Cake -MySQL;
use DBIO::Cake -SQLite;

Other import flags:

-inflate_datetime    load InflateColumn::DateTime automatically
-inflate_json        inflate json columns to hashrefs
-inflate_jsonb       inflate jsonb columns (PostgreSQL)
-retrieve_defaults   fetch server-side defaults after insert

The type registry on the active Storage class (cake_defaults(), type_info($name)) drives driver-aware column type resolution.

INTEGRATED COMPONENTS

The following features were previously separate CPAN distributions or required load_components. In DBIO they are part of core and need no explicit loading unless noted.

DBIO::Timestamp (was DBIx::Class::TimeStamp)

Automatic created_at / updated_at timestamps. Configure per column:

col created_at => timestamp, on_create;
col updated_at => timestamp, on_update;

Or use the helpers available in all three styles:

col_created 'created_at';        # set on insert
col_updated 'updated_at';        # set on insert and update
cols_updated_created();          # both with default names

Available via DBIO::Timestamp. No load_components needed in Cake or Candy; Vanilla code can still load it explicitly.

Row helpers (was DBIx::Class::Helpers)

The following helpers from DBIx::Class::Helpers are integrated directly into DBIO::Row and require no load_components call:

"TO_JSON" in DBIO::Row - serializable hashref, excludes blob/text columns
"serializable_columns" in DBIO::Row - list of serializable column names
"self_rs" in DBIO::Row - a ResultSet scoped to this row
"get_storage_value" in DBIO::Row - original DB value before in-memory changes
"before_column_change" in DBIO::Row - fire a callback before a column is modified
"after_column_change" in DBIO::Row - fire a callback after a column is modified
"around_column_change" in DBIO::Row - wrap a column update with an around-style hook
"proxy_resultset_method" in DBIO::Row - expose a ResultSet with_* method as a row accessor

These come from DBIx::Class::Helper::Row::StorageValues, DBIx::Class::Helper::Row::OnColumnChange, and DBIx::Class::Helper::ResultSet::ProxyResultSetMethod.

IntrospectableM2M (was DBIx::Class::IntrospectableM2M)

Many-to-many introspection is integrated into DBIO::Relationship::ManyToMany. The _m2m_metadata structure is populated automatically when you declare a many_to_many relationship. No load_components needed.

Accessible fields: accessor, relation, foreign_relation, rs_method, add_method, set_method, remove_method.

DBIO::UUIDColumns (was DBIx::Class::UUIDColumns)

Auto-generate UUID values on insert. Mark columns with:

uuid_columns('id');            # Vanilla/Candy
col id => uuid, auto_inc;      # Cake (uses uuid_columns internally)

The UUID class defaults to the first available among Data::UUID, UUID, UUID::Random. Override with uuid_class().

See DBIO::UUIDColumns.

DBIO::EncodedColumn

One-way encoding of column values (passwords, tokens). Mark a column:

__PACKAGE__->add_columns(
    password => {
        data_type       => 'varchar',
        size            => 100,
        encode_column   => 1,
        encode_class    => 'Digest',
        encode_args     => { algorithm => 'SHA-256', format => 'hex' },
        encode_check_method => 'check_password',
    },
);

The column is encoded transparently on write. The check_password (or whatever name you give encode_check_method) method is generated automatically on the Result class.

Storage format: dbio$<algo>$<salt_b64>$<digest_b64>.

See DBIO::EncodedColumn.

DBIO::HashAccessor

Accessor generation for serialized hash columns (JSON, YAML, MessagePack):

__PACKAGE__->add_hash_accessor('metadata', 'metadata_col');

Generates: metadata(), metadata_hash(), metadata_hash_delete(), metadata_push(), metadata_shift().

See DBIO::HashAccessor.

InflateColumn::Serializer (JSON, YAML, MessagePack)

Serialize complex Perl structures to a text/blob column. Available via load_components('InflateColumn::Serializer'). Serializer options: JSON, YAML, Sereal, MsgPack.

PopulateMore (was DBIx::Class::PopulateMore)

Enhanced populate() that supports cross-source references, fixture data from files, and batch inserts. Integrated into DBIO::Schema. The invocation syntax is unchanged:

$schema->populate([ Users => ... ]);

NEW CONCEPTS

These features did not exist in DBIx::Class in any form.

DBIO::ChangeLog - audit trail per Result class

Automatic row-level change tracking. Add to a Result class:

__PACKAGE__->load_components('ChangeLog');

DBIO creates a companion table <table>_changelog automatically. By default every column change is recorded (INSERT, UPDATE, DELETE). Exclude sensitive columns:

__PACKAGE__->changelog_exclude_columns(qw/ password_hash session_token /);

Changes are stored as JSON. Methods:

$row->changelog_entries();          # ResultSet of log rows
$schema->changelog_serialize_changes($hashref);
$schema->changelog_deserialize_changes($json_str);

PostgreSQL storage can use native jsonb for the changes column. The version table was renamed from dbix_class_schema_versions to dbio_schema_versions.

See DBIO::ChangeLog and DBIO::Schema::ChangeLog.

DBIO::AccessBroker - credential source interface

A storage-agnostic interface for credential lifecycle management. A broker is a CredentialSource: it supplies the connect info for one backend identity. It does not route and does not own a host list - read/write routing and the master/replicant topology belong to DBIO::Replicated. Pass a broker to connect() in place of a DSN; the storage detects it and attaches it:

my $schema = MyApp::Schema->connect(
    DBIO::AccessBroker::Static->new(
        dsn      => $dsn,
        username => $user,
        password => $pass,
    )
);

The storage calls connect_info_for_storage($storage) on the broker before each connection. One credential can serve many servers via a $broker->for_host($host) view.

Built-in broker classes:

DBIO::AccessBroker::Static - single DSN, drop-in replacement
DBIO::AccessBroker::Vault - rotating credentials with TTL
DBIO::AccessBroker::HostBound - one credential identity pinned to one host

Build a custom broker by subclassing DBIO::AccessBroker and implementing connect_info_for_storage, needs_refresh, and refresh.

DBIO::Moo and DBIO::Moose - OO framework bridges

Use Moo or Moose attributes alongside DBIO column accessors in the same Result, ResultSet, or Schema class.

package MyApp::Schema::Result::Artist;
use DBIO::Moo;
use DBIO::Cake;

col artistid => serial, auto_inc;
col name     => varchar(100);
primary_key 'artistid';

has display_name => (is => 'lazy');
sub _build_display_name { 'Artist: ' . $_[0]->name }

1;

DBIO::Moo and DBIO::Moose wire the framework constructor (FOREIGNBUILDARGS for Moo, BUILDARGS for Moose) into DBIO's new() so that DBIO column data is routed correctly and Moo/Moose attributes are filtered from DBIO's column constructor.

Combinations with Cake and Candy are fully supported. Use DBIO::Cake with use DBIO::Moo in the same file.

See DBIO::Moo and DBIO::Moose.

Async storage interface

DBIO::Storage::Async defines a storage-agnostic async interface (Phase 1 + 2). Queries return Future objects. Two concrete implementations bypass DBI entirely:

DBIO::PostgreSQL::Async (DBIO-PostgreSQL-Async) - libpq via EV::Pg; adds LISTEN/NOTIFY, COPY IN/OUT, and request pipelining.
DBIO::MySQL::Async (DBIO-MySQL-Async) - the MariaDB client via EV::MariaDB; adds pipelining and connection-pool transaction pinning.

The core async interface is in DBIO::Storage::Async. Application code that targets multiple backends should program against that interface.

Type registry on Storage

Each driver's Storage class exposes:

$schema->storage->cake_defaults()        # hashref of driver-preferred options
$schema->storage->type_info($type_name)  # { components => [...], cake_options => {...} }

The Cake DSL reads this registry when resolving column types so that, for example, col data => jsonb on a PostgreSQL storage automatically activates InflateColumn::JSONB.

DBIO::Storage::DateTimeFormat - shared driver datetime base

DBIx::Class drivers each hand-rolled a private DateTime::Format::* wrapper package inside their storage class, typically require-ing DateTime::Format::Strptime at runtime. DBIO replaces these with a shared base, DBIO::Storage::DateTimeFormat. A driver's datetime format class declares its strptime patterns as class data and may name a preferred CPAN DateTime::Format::* class:

package DBIO::DriverName::DateTime::Format;
use base 'DBIO::Storage::DateTimeFormat';
__PACKAGE__->preferred_format_class('DateTime::Format::DriverName'); # optional
__PACKAGE__->datetime_parse_pattern('%Y-%m-%d %H:%M:%S');
__PACKAGE__->datetime_format_pattern('%Y-%m-%d %H:%M:%S');

When the preferred class is installed it handles parsing and formatting; otherwise the declared patterns are used via DateTime::Format::Strptime (a hard dependency of core, so drivers declare nothing extra). The pattern fallback round-trips identically to the preferred class.

UTILITY CHANGES

DBIO::Util replaces DBIx::Class::_ENV_

The internal DBIx::Class::_ENV_ pseudo-package has been removed. All runtime constants and utility functions are now in DBIO::Util:

use DBIO::Util qw( is_windows help_url scope_guard ... );

Exported functions include:

is_windows   is_dev_release   old_mro   has_ithreads
dbiotest     peepeeness       shuffle_unordered_resultsets
help_url     iv_size          os_name

sigwarn_silencer   modver_gt_or_eq   fail_on_internal_wantarray
refdesc  refcount  hrefaddr  scope_guard
quote_sub  qsub  perlstring  serialize  dump_value
UNRESOLVABLE_CONDITION

dir_path   file_path   parent_dir   slurp_file   write_file
mkpath     rmtree      split_name   class_path

firstidx  uniq  apply  array_eq
is_plain_value   is_literal_value

Path::Class is no longer a dependency; use the DBIO::Util path helpers instead.

DBIOx namespace

The DBIOx:: namespace is reserved for official DBIO extensions that are not part of the core distribution - analogous to DBIx:: but scoped to the DBIO ecosystem.

REMOVED FEATURES

Class::DBI compatibility (CDBICompat)

The DBIx::Class::CDBICompat layer and the legacy DB.pm shim have been removed entirely. There is no replacement. If you depend on Class::DBI compatibility, stay on DBIx::Class or port your code.

Hide-from-PAUSE package trick

Internal packages no longer use the package # hide from PAUSE\nFoo::Bar; trick. Meta::NoIndex in the distribution's META.yml handles PAUSE exclusion instead.

Removed external dependencies

SQL::Abstract::Classic - replaced by SQL::Abstract
Path::Class - replaced by helpers in DBIO::Util
Sub::Name - no longer used
SQL::Abstract::Util - merged into SQL::Abstract core

TESTING INFRASTRUCTURE

DBIO::Test - database-free unit testing

Core tests must not use a real database. DBIO::Test provides a mock storage back-end:

use DBIO::Test;
my $schema = DBIO::Test->init_schema();    # fake storage, no DBI

my $storage = $schema->storage;
$storage->mock(qr/SELECT .* FROM artist/, [
    { artistid => 1, name => 'Tori Amos' },
]);

Driver-specific tests (real SQL, real DBI) belong in the driver distributions.

Shared test schemas

Test schemas shared across the core and driver distributions live in DBIO::Test::Schema::* under dbio/lib/:

DBIO::Test::Schema::Vanilla
DBIO::Test::Schema::Candy
DBIO::Test::Schema::Cake
DBIO::Test::Schema::Moo
DBIO::Test::Schema::Moose

Load driver-specific result classes with the hashref form:

DBIO::Test::Schema->load_classes({
    'DBIO::PostgreSQL::Test' => ['SequenceTest'],
});

Never redefine a shared schema class inline inside a driver test.

Environment variables renamed

All test environment variables were renamed from DBICTEST_* to DBIO_TEST_*:

DBIO_TEST_PG_DSN         PostgreSQL DSN
DBIO_TEST_MYSQL_DSN      MySQL/MariaDB DSN
DBIO_TEST_SQLITE_DSN     SQLite DSN (rarely needed, usually in-memory)

MIGRATION QUICK-REFERENCE

DBIx::Class concept               DBIO equivalent
---------------------------------------------------------------------
DBIx::Class::*                    DBIO::*
use base 'DBIx::Class::Core'      use base 'DBIO::Core'  (Vanilla)
                                  use DBIO::Candy;       (Candy)
                                  use DBIO::Cake;        (Cake)
sql_limit_dialect / emulate_limit apply_limit on SQLMaker subclass
SQL::Abstract::Classic            SQL::Abstract (>= 1.99)
DBIx::Class::TimeStamp            DBIO::Timestamp (integrated)
DBIx::Class::Helpers              DBIO::Row helpers (integrated)
DBIx::Class::IntrospectableM2M    ManyToMany (integrated)
DBIx::Class::UUIDColumns          DBIO::UUIDColumns (integrated)
DBIx::Class::Replicated           DBIO::Replicated (in core)
DBIx::Class::CDBICompat           removed (no replacement)
DBICTest::*                       DBIO::Test::*
DBICTEST_* env vars               DBIO_TEST_* env vars
dbix_class_schema_versions        dbio_schema_versions
SQL::Translator (DDL/deploy)      native Deploy per driver (optional)

SEE ALSO

DBIO::Manual - documentation overview
DBIO::Manual::DocMap - full documentation map
DBIO::Manual::Migration - step-by-step DBIx::Class to DBIO conversion
DBIO::Base - meta-infrastructure base class
DBIO::Candy - import-sugar layer
DBIO::Cake - DDL-like DSL layer
DBIO::Timestamp - automatic timestamp columns
DBIO::UUIDColumns - automatic UUID columns
DBIO::EncodedColumn - one-way column encoding
DBIO::ChangeLog - row-level audit trail
DBIO::AccessBroker - connection routing interface
DBIO::Moo - Moo attribute bridge
DBIO::Moose - Moose attribute bridge
DBIO::Replicated - replicated storage
DBIO::Test - database-free test infrastructure
DBIO::Util - utility functions
DBIO::Storage::DateTimeFormat - shared driver datetime base

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.