Revision history for DBIO-MySQL
0.900004 2026-11-08
* Storage
- The SQL dialect follows the connected server, not the DBD driver:
upsert row alias, locking-read clause and replica status. A modifier
the server lacks is refused locally.
- Capabilities declared: supports_exists_query, insert_returning from
MariaDB 10.5, transactional_ddl 0, supports_upsert_where 0;
INTERSECT and EXCEPT are refused for an older declared server.
- The session runs with STRICT_ALL_TABLES unless a strict mode is set,
so an overlong value is refused, not cut.
- Binary columns bind as SQL_BINARY; under DBD::mysql with
mysql_enable_utf8mb4 text binds as characters, also in populate in
void context. Pass characters for text and bytes for binary columns.
- mysql_auto_reconnect is off from connect on, named per DBD (mysql_*
or mariadb_*); an explicit value is kept. load_components('MySQL')
works over dbi:MariaDB: and the other way round.
- DBD::MariaDB: a forked child's exit leaves the parent's connections
alone (DBIO::MySQL::MariaDBForkGuard).
- The storage declares the mysql SQL dialect, so a backslash in a
literal is an escape for the DDL splitter. A HostBound view over a
positional DSN binds its host and port.
- deployment_statements asks no server, and a schema always declares
its driver.
- An error text carries the type, the column, the key or constraint and
the number of binds, never a bound value: the server's own quotes
of a value (Duplicate entry, Incorrect ... value, the excerpt after
"near") are masked as well as the values themselves, short ones
included. Both DBDs, client and server side prepare, and the
future_io transport.
* SQL
- $rs->upsert renders INSERT ... ON DUPLICATE KEY UPDATE. A demanded
conflict target on a table with further unique keys throws unless
conflict_target => 'any'.
- order_by with -nulls => 'first' or 'last' is emulated by a leading
IS NULL sort, in the PostgreSQL row order.
* Types
- uniqueidentifier is VARCHAR(36), clob is LONGTEXT and money is
DECIMAL(19,4); an enum or set column writes its members from extra
=> { list => [...] } and a bit column keeps its size.
- The decimal, character, varchar, varbinary and binary aliases take
an explicit size, a precision-only numeric renders DECIMAL(p), and a
column without is_nullable deploys as NOT NULL.
* DDL
- Every identifier is written in backticks, so reserved words and
names like my-col install and upgrade.
- mysql_index renders FULLTEXT and SPATIAL as index classes and BTREE
and HASH as USING; invalid values are refused. install_ddl also
reads idx and indices.
- Tables are written in foreign key order, cycles close with ALTER
TABLE ... ADD FOREIGN KEY, and the key of an AUTO_INCREMENT column
outside the primary key stands in the CREATE TABLE.
- Foreign keys carry ON UPDATE and ON DELETE from the belongs_to
attributes; SET DEFAULT and unknown words throw. A relationship to a
view writes no foreign key.
- A foreign key has the fixed name <table>_<columns>_fk, the same in
install and upgrade; duplicates become one constraint and clashing
names get a hash suffix.
- Views are written after the views they read; a cycle is refused,
naming the views.
- DEFAULT values come from one helper (DBIO::MySQL::DefaultValue):
literals are escaped, expressions from the catalog are not quoted. A
refused column declaration reaches the caller.
- A table named database.table is made in that database; the driver
creates no database. A view or foreign key with a dot is refused.
* Deploy
- DBIO::MySQL::Deploy takes apply and upgrade from DBIO::Deploy::Base:
a missing database and rows a new NOT NULL column cannot fill are
refused before the first statement; dry_run asks the server nothing.
- A column change that can alter stored values (narrower type, signed
to unsigned, another character set, enum or set losing a member)
needs allow_destructive; lossless type pairs and wider sizes do not.
- add_drop_table drops the declared views before the tables, sync and
async, so a second deploy over a filled schema runs.
* Introspect
- is_pk is read from the index PRIMARY; defaults are literals or
expressions (default_is_expression), and ON UPDATE CURRENT_TIMESTAMP
is read as on_update.
- A nullable column without a default has none on MariaDB too, and an
explicit NO ACTION foreign key of a MariaDB reads as RESTRICT.
* Diff
- An upgrade gives an existing table the primary key the target names,
drops the key of a target without one, and handles the foreign keys
and AUTO_INCREMENT columns around the change.
- Unique indexes are diffed like every other index; an index is
dropped before the column changes and created after them, and an
index a foreign key needs is replaced before its drop.
- Foreign keys stand around everything else: dropped first, added
last. The last foreign key of a table is dropped when it leaves the
schema.
- The foreign key of a new table follows an index the same upgrade
creates over its columns, and ADD COLUMN follows the declaration
order of the schema (contributed by Olaf Alders).
- An upgrade from an empty database creates tables in foreign key
order. Tables of other databases (database.table) are created,
compared and altered there.
- Views are diffed as views (DBIO::MySQL::Diff::View); views that read
each other are dropped and created in dependency order, and a
changed definition counts as changing, not destructive.
- A table whose collation differs is converted with CONVERT TO where
the data provably survives; a narrowing, a TEXT, ENUM or SET column
of a widening and a foreign key over text are refused.
- CREATE TABLE names the collation install_ddl writes, a column off
its table's character set or collation is written explicitly, and ON
UPDATE CURRENT_TIMESTAMP is compared and written.
- DECIMAL(p) compares as decimal(p,0), and the default of a column the
schema leaves without one is removed, so an upgraded schema diffs
empty.
- A FULLTEXT or SPATIAL index added through a diff is rendered as such
(DBIO::MySQL::IndexType); the POD names the MariaDB FULLTEXT rebuild
behaviour.
* Async
- The future_io transport fills the deploy seam with the generator of
the sync pipeline, so deploy_async writes the same DDL;
add_drop_table quotes tables with backticks.
- LISTEN/NOTIFY is emulated over a notify table (ADR 0008): listen,
unlisten and notify work on { async => 'future_io' };
deploy_notify_table creates the table.
- Pool connections are fork-safe and do not auto-reconnect unless the
connect attributes say so.
- The DBD binding (mysql_* or mariadb_*) is chosen per connection, so
every component and DSN combination connects; a DSN without a driver
part is refused.
- connect($broker, { ... }) takes DBD attributes and pool_size beside
an AccessBroker, and attributes in the fifth element or the
single-hash form reach every pool and listen connection.
- A dead or idle-expired pool connection is retired before it is
handed out, and _collect_result reads the further result sets of a
CALL.
- insert_async resolves with the id of its own statement, also when
inserts overlap.
- The connections get the session setup of the sync storage: strict
sql_mode, SQL_BINARY binds, text as characters.
* Security
- Connect errors of the future_io transport read *** for the password.
* Tests
- DBIO::MySQL::Test::Conformance answers the shared DDL, Diff and Bind
case tables of core against MySQL and MariaDB, each case in a
database of its own.
- A fresh maint/docker stack runs the whole live suite as dbio with no
manual step (contributed by Olaf Alders); the MySQL test image is
pinned to 8.0.30.
- Live tests skip, naming the missing GRANT, when the account cannot
create a diff's temporary database, and a DBD that is not installed
is skipped (contributed by Olaf Alders).
* Documentation
- POD, README and ADRs describe the code as it behaves, and the README
states Perl 5.20 as the requirement.
* Dependencies
- Requires DBIO 0.900004.
0.900001 2026-07-12
* Storage
- Add DBIO::MySQL::Storage::Async and DBIO::MySQL::Storage::MariaDB::Async,
the real future_io async adapter. It drives whichever DBD the DSN
named: the base class carries DBD::mysql's async binding (async /
mysql_fd / mysql_async_ready / mysql_async_result / mysql_insertid),
and the MariaDB subclass overrides five DBD-specific primitives with
DBD::MariaDB's mariadb_* binding -- mirroring the sync mysql_insertid /
mariadb_insertid split, so a dbi:mysql: connection uses DBD::mysql's
async and a dbi:MariaDB: connection uses DBD::MariaDB's. Subclasses
DBIO::Async::Storage and fills only the DB-specific seams
(_submit_query / _collect_result, socket fd, insertid folded onto the
autoinc PK in place of RETURNING); Model-B orchestration is inherited.
Convention-resolved off the leaf driver storage (ADR 0030) — no
registration. Pipeline is unsupported (one async query per connection).
- Register the 'ev' async mode (core ADR 0030): connect(..., { async => 'ev' })
now resolves DBIO::MySQL::EV::Storage via the core mode registry;
this shadows the generic modes registered on DBIO::Storage::DBI
and replaces the obsolete async_backend() / load_components('MySQL::EV')
opt-ins. Requires the optional dbio-mysql-ev dist at first use;
absent → the canonical "install DBIO::MySQL::EV::Storage" croak (ADR 0030).
- Reference-driver compliance for the storage-layer composition model
(karr #22, core #70): storage_type is written at exactly two sites,
both driver Schema components (DBIO::MySQL -> DBIO::MySQL::Storage,
DBIO::MySQL::MariaDB -> DBIO::MySQL::Storage::MariaDB) -- the
driver's job; extensions register plain storage LAYERS instead of
subclassing storage_type. Inherits transport_capabilities =>
(on_connect_replay); MySQL/MariaDB have no LISTEN/NOTIFY/COPY
(documented), pipelining stays on the ev backend. The driver was
already future_io-compliant -- MySQL keeps '?' natively, so
_transform_sql is identity, run once inside the inherited
DBIO::Async::Storage transport (no double-shaping). No behavioural
change -- POD/comments + driver-level structural tests only.
* Documentation
- Add docs/adr/0030-async-mode-registration.md — per-repo operationalisation
of the core async mode registry mechanism for the MySQL driver
- Add docs/adr/0007-async-transport-mirrors-the-sync-dbd-split.md —
records why the future_io adapter mirrors the sync mysql_*/mariadb_*
DBD split (one base class plus a MariaDB-flavoured subclass) instead
of a single generic transport
* Security
- Add SECURITY.md following the CPAN Security Group's Guidelines for
Adding a Security Policy to Perl Distributions (v1.5.0): email-first
private reporting to the maintainer
* Bugfixes
- Diff: suppress the standalone DROP INDEX that collided with DROP TABLE
in a full-schema sync (karr #23). When a whole table is removed in the
same diff pass, DROP TABLE already drops that table's own indexes;
as_sql runs tables before indexes, so the later DROP INDEX ... ON
<table> targeted an already-gone table and failed on a real server.
DBIO::MySQL::Diff now threads the source/target tables sections into
Diff::Index->diff (as it already does for Diff::Column /
Diff::ForeignKey), and Diff::Index skips the drop for any index whose
owning table is itself being dropped. Indexes removed from a surviving
table are unaffected. Backward compatible: absent table args -> no
suppression.
- Fix the native introspect->diff round-trip against a real MySQL 8
server, which produced phantom diffs (every column reported as a
spurious add) and a zero-date write failure: information_schema
result columns come back UPPERCASE on MySQL 8, so Columns/
ForeignKeys/Indexes now force NAME_lc on their fetches; MySQL
8.0.17+ drops the integer display width in information_schema
(bigint(20) -> bigint), now normalised on both sides in
Diff::Column (tinyint(1) and the semantic decimal/char widths stay
untouched); target_from_compiled no longer synthesises a literal
'NULL' default for nullable no-default columns (information_schema
reports SQL NULL as undef, not the string); and a new
connect_call_set_zero_date_permissive on_connect seam relaxes
strict sql_mode on every (re)connect, not just the deploy-time
handle, so zero-date writes survive reconnects
- Remove t/30-versioning.t and its DBIOVersion_v1/v2/v3 fixtures; the
test exercised DBIO::Schema::Versioned, which core retired (its
upgrade path depended on the hard-deprecated create_ddl_dir, with
no native replacement -- core karr #74)
- Remove t/01-access-broker-api.t, which required the undeclared and
since-removed DBIO::AccessBroker::Credentials and broke the test
suite on a clean install (Codeberg #1)
- Make the table-name assertions in t/50-introspect-contract.t
quote-agnostic; the code generator emits double-quoted strings via
B::perlstring
* Dependencies
- Require DBIO 0.900001 (central private-attr strip in
DBIO::Storage::Async::connect_info, on which both the sync and the
future_io path rely)
- DBIO::Async, the shared future_io backend base the async adapter
subclasses, is deliberately NOT declared in cpanfile: it is
optional and convention-resolved at runtime (ADR 0030), only
pulled when { async => 'future_io' } is used, so a sync-only
install never drags in the async stack. Core's future_io resolver
now names the missing module and the distribution that provides
it when the adapter can't load (core karr #78), so a separate
recommends pointer is no longer needed.
- Add a runtime suggests on DBD::mysql alongside the recommended
DBD::MariaDB, so both supported DBD drivers are advertised in the
metadata (DBD::MariaDB stays the default, DBD::mysql the alternative)
- Drop a stray test-phase recommends on Kubernetes::REST, an unused
copy-paste leftover from another distribution
* Tests
- Add maint/docker/ (karr #21): a Docker test image for a buildable,
live DBD::mysql -- ubuntu:24.04's libmysqlclient-dev is still the
genuine Oracle MySQL client (Debian switched its default provider to
MariaDB, and the MySQL APT repo's own signing key has expired),
sidestepping both the wrong-flavour and wrong-signature roadblocks.
Live-verified the mysql_* future_io transport (t/55) against a real
mysql:8.0 server -- non-blocking select/insert/select_single,
mysql_insertid folded onto the autoinc PK, txn_do_async commit and
rollback, and the high-level create_async/all_async API -- mirroring
the already-live mariadb_* subclass
0.900000 2026-06-23
First release. MySQL and MariaDB driver for DBIO, with native schema
deployment, introspection, diffing and DDL generation.
* Driver
- MySQL/MariaDB storage with autodetection of dbi:mysql: DSNs via
the DBIO driver registry
- DBIO::MySQL schema component and -mysql shortcut
- DBIO::MySQL::Storage and DBIO::MySQL::Storage::MariaDB
- DateTime::Format::MySQL datetime parsing
* SQLMaker
- DBIO::MySQL::SQLMaker with apply_limit() (LIMIT ?, ? syntax) and
FOR SHARE locking
- DBIO::MySQL::SQLMaker::MariaDB maps FOR SHARE to LOCK IN SHARE
MODE for MariaDB
* Schema management
- DBIO::MySQL::Introspect (Tables, Columns, Indexes, ForeignKeys)
via information_schema
- DBIO::MySQL::DDL generator from Result classes with
ENGINE/CHARSET/COLLATE handling, view and shared-table handling
- DBIO::MySQL::Diff (Table, Column, Index, ForeignKey) with inlined
columns on CREATE TABLE and full MODIFY COLUMN support
- DBIO::MySQL::Deploy: install/diff/apply/upgrade via test-deploy-
and-compare against a temporary database
- Storage routes DBIO::Schema->deploy() through DBIO::MySQL::Deploy
* Result component
- DBIO::MySQL::Result with mysql_engine, mysql_charset,
mysql_collate and mysql_index declarations consumed by the DDL
generator
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
Go to GitHub issues (only if GitHub is preferred repository)