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_framesfor 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) -libpqviaEV::Pg; adds LISTEN/NOTIFY, COPY IN/OUT, and request pipelining.DBIO::MySQL::Async(DBIO-MySQL-Async) - the MariaDB client viaEV::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 bySQL::AbstractPath::Class- replaced by helpers in DBIO::UtilSub::Name- no longer usedSQL::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
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.