NAME
DBIO::Firebird::Diff - Compare two introspected Firebird models
VERSION
version 0.900004
DESCRIPTION
DBIO::Firebird::Diff compares two introspected Firebird database models (as produced by DBIO::Firebird::Introspect) and produces a list of structured diff operations. These operations can then be rendered to SQL or a human-readable summary.
my $diff = DBIO::Firebird::Diff->new(
source => $current_model,
target => $desired_model,
);
if ($diff->has_changes) {
print $diff->as_sql;
print $diff->summary;
}
Diff operations are generated in dependency order: views to drop, foreign keys to drop, tables, primary keys and unique constraints to drop, indexes to drop, columns, indexes to create, primary keys and unique constraints to add, foreign keys to add, then the views to create or replace.
The target model's indexes come from "install_ddl" in DBIO::Firebird::DDL, so they include the canonical index declaration of the result source (idx, indices); what that method refuses is refused here too.
METHODS
as_sql
The statements of all operations as one script -- or, if the diff holds a column change the server refuses, a DBIO::Exception that lists every such column and renders nothing. See "COLUMN CHANGES THE SERVER REFUSES".
NAMES THAT DIFFER ONLY IN CASE
Names are compared exactly, the way Firebird compares quoted names: ARTIST and artist are two tables. One situation makes that comparison destructive, and the diff refuses it instead of rendering it.
Firebird folds an unquoted identifier to upper case, and this distribution quotes every identifier it emits ("install_ddl" in DBIO::Firebird::DDL). A database whose objects were created unquoted -- by hand or with another tool -- holds ARTIST where the schema says artist. Compared exactly, that is one table to drop and one to create, and applying it empties the table.
So when a name only the source model has and a name only the target model has are equal but for letter case, building the operations dies with a DBIO::Exception. It lists every such pair of the whole diff, not just the first. No operation is handed out and no statement rendered, the unrelated ones included: has_changes, as_sql, summary and operations all die, and so do "apply" in DBIO::Deploy::Base and "upgrade" in DBIO::Deploy::Base, before they send anything. "diff" in DBIO::Deploy::Base itself returns: it builds the two models and this object, and the operations are computed on first use.
This is checked for the three kinds of object the diff has operations for: tables, and the columns and indexes of a table both models have under one name. The columns and indexes of a table that is itself such a pair are not listed again. Only unpaired names count: if the database has both ARTIST and artist and the schema has artist, then ARTIST is a table the schema does not have and is dropped like any other; if the schema has both and the database has ARTIST, then artist is new.
There is no switch to diff anyway. The ways out, as Firebird 5.0 has them:
- Tables
-
Declare the table in the schema as the database stores it --
__PACKAGE__->table('ARTIST'), and its columns in their stored spelling too, they were folded the same way. Nothing in the database changes, and the ORM, which quotes, then addresses the table that is there.Or move the data. Firebird cannot rename a table: create
"artist", copy the rows over from"ARTIST", drop"ARTIST". The two exist side by side in between -- and while they do,ARTISTis unpaired, so anupgraderun before the rows are copied drops it. - Columns
-
Declare the column in the schema as it is stored, or rename it:
ALTER TABLE "cd" ALTER COLUMN "TITLE" TO "title";Firebird refuses that while a
PRIMARY KEY,UNIQUEorFOREIGN KEYconstraint uses the column; drop the constraint, rename, add it back. - Indexes
-
Firebird cannot rename an index, and an index holds no data:
DROP INDEX "CD_TITLE_IDX", and the next diff creates"cd_title_idx".This is the one a schema declaring the stored spelling still meets. "install_ddl" in DBIO::Firebird::DDL names the index of a unique column
<table>_<column>_idx, with that lower-case suffix, so the oldARTIST_NAME_IDXstands against"ARTIST_NAME_idx", and nothing declared in the schema changes the suffix.
The op classes do not check this themselves: their diff class methods ("diff" in DBIO::Firebird::Diff::Table and its siblings) return the plain drop and create operations when called directly.
COLUMN CHANGES THE SERVER REFUSES
Some column changes Firebird refuses outright: narrowing a VARCHAR, a character column into a numeric one, a digit count that shrinks, any type change of a column a PRIMARY KEY, UNIQUE or FOREIGN KEY constraint uses. Sent, the statement fails on the server and leaves an upgrade half applied, and the same difference is back on the next run.
The diff therefore keeps reporting such a change -- has_changes is true, summary and operations list it -- but refuses to render it: as_sql dies with a DBIO::Exception naming every such column with table, old and new type and the reason, and renders nothing, the unrefused statements included. "apply" in DBIO::Deploy::Base and "upgrade" in DBIO::Deploy::Base render the whole script before they execute the first statement, so they die before they touch the database. The rules, as measured on the server, and what is not refused: "COLUMN CHANGES THE SERVER REFUSES" in DBIO::Firebird::Diff::Column.
VIEWS
The introspected model lists a view in tables (kind => 'view') with its definition, rdb$view_source, as view_source, and its columns in columns. The diff writes CREATE VIEW for a view the schema has and the database lacks, and DROP VIEW for a view only the database has; a view with another definition is dropped and created again (DBIO::Firebird::Diff::View). Definitions are compared after leading and trailing whitespace is cut and every run of whitespace is one blank; any other difference in the text is a change. The columns of a view are never altered like a table's.
The order: the views to drop -- removed ones and changed ones -- come first, then the tables, columns, indexes and constraints, then the views to create (new ones and the changed ones again) -- so a view is gone before the table or column it read is dropped (a changed view that no longer reads a column does not stop that column from going) and exists after the tables and columns it reads. Among views, one that reads another (its definition names it) is dropped before and created after it.
The diff does not guess what the server accepts. When Firebird refuses a statement (a view that other objects depend on, an unchanged view that reads a changed one -- it is not dropped with it --, a definition over a column that is dropped in the same diff), the server's error is raised from apply/upgrade as it is. A name that is a table on one side and a view on the other is the exception: it is refused before any statement ("refusal" in DBIO::Firebird::Diff::Table).
PRIMARY KEYS, FOREIGN KEYS AND UNIQUE CONSTRAINTS
The target model is the temp database the schema was installed into, so it holds the foreign keys and unique constraints "install_ddl" in DBIO::Firebird::DDL writes. A table that is new gets its constraints as ALTER TABLE ... ADD CONSTRAINT after all CREATE TABLE statements of the diff. A table that exists on both sides gets every difference in a constraint -- its primary key too, compared by its columns in key order -- written as DROP CONSTRAINT and ADD CONSTRAINT, a changed one as both. The order is the one of "DESCRIPTION": foreign keys go first and come last, so no key is dropped under a foreign key and none is added over a column that is not there yet; an index is dropped before the column it covers. Check constraints are not part of the model and not touched. See DBIO::Firebird::Diff::Constraint.
SEE ALSO
DBIO::Firebird::Deploy - orchestrates introspection and diff
DBIO::Firebird::Introspect - produces the models being compared
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.