NAME
DBIO::Firebird::Diff::Column - Diff operations for Firebird columns
VERSION
version 0.900004
DESCRIPTION
Column-level diff operations for Firebird. An op renders as one of
ALTER TABLE "t" ADD "c" varchar(100) DEFAULT 'x' NOT NULL;
ALTER TABLE "t" DROP "c";
ALTER TABLE "t" ALTER "c" TYPE decimal(12,3);
ALTER TABLE "t" ALTER "c" SET NOT NULL; -- or DROP NOT NULL
ALTER TABLE "t" ALTER "c" SET DEFAULT 'x'; -- or DROP DEFAULT
Table and column are always double-quoted (see "install_ddl" in DBIO::Firebird::DDL for why), and the type always carries the model's size, rendered by "render_size" in DBIO::Firebird::Type: the size is part of the type, so a changed length or a changed (precision,scale) is an ALTER ... TYPE like a changed type name is. In an ADD the default comes before NOT NULL; Firebird parses the two in that order only.
The statement is the plain one for the difference found -- unless the server is known to refuse it. See "COLUMN CHANGES THE SERVER REFUSES".
ATTRIBUTES
lossy_pairs
my %pairs = %DBIO::Firebird::Diff::Column::LOSSLESS_PAIRS;
The old->new keys "lossy" hands to "column_change_lossy" in DBIO::Diff::Compare; see "COLUMN CHANGES THAT CAN LOSE DATA".
METHODS
diff
my @ops = DBIO::Firebird::Diff::Column->diff(
$source_cols, $target_cols, $source_tables, $target_tables,
$source_unique_constraints, $source_foreign_keys);
The last two arguments are the unique_constraints and foreign_keys sections of the source model. They tell an alter operation which constraints use its column (see "COLUMN CHANGES THE SERVER REFUSES"); left out, only the is_pk of the column counts.
refusal
my $reason = $op->refusal; # undef, or why the server refuses
For an alter operation that changes the type or size of a column: the reason the Firebird server always refuses the change, as one line of text, or undef if it takes it (or if this module has no rule for it). See "COLUMN CHANGES THE SERVER REFUSES". Every other operation returns undef.
as_sql
Dies with a DBIO::Exception instead of returning a statement for an alter that "refusal" gives a reason for.
lossy
my $lossy = $op->lossy;
True for an alter whose type or size can alter or lose stored values ("COLUMN CHANGES THAT CAN LOSE DATA"); false for everything else.
summary
COLUMN CHANGES THE SERVER REFUSES
Firebird does not accept every type change. It widens a VARCHAR and refuses to narrow one, does not turn a character column into a numeric one, does not retype a column a constraint depends on. Sent anyway, such a statement dies on the server with its own message, upgrade stops half way, and the same difference is back on the next run. So the diff refuses them itself, earlier and clearer.
The difference is still reported. diff returns the operation, has_changes is true and summary lists the column. What refuses is generating the statement: as_sql of the operation, and so "as_sql" in DBIO::Firebird::Diff, "apply" in DBIO::Deploy::Base and "upgrade" in DBIO::Deploy::Base, die with a DBIO::Exception naming table, column, old and new type and the reason -- for every such column of the diff at once. apply renders the whole script before it executes its first statement, so nothing of an upgrade that contains a refused change runs.
Only what the server always refuses is refused. The rules were measured against Firebird 5.0.4, each case on an empty table and on one with rows (the answer did not differ), and t/28-diff-column-refused-live.t walks the predicate against a server, pair by pair. The type of a column is changed only if it is none of these:
Narrowing
CHAR/VARCHAR(andBINARY/VARBINARY): a new length below the old one. Widening, andCHARtoVARCHARof the same length, are accepted.A character type to anything that is not a character type; and anything to
BOOLEANor to aBLOB, and aBLOBto anything else.A conversion between the other types the server has none of. It converts within the storage, never to a smaller one:
SMALLINTtoINTEGER,BIGINT,INT128,FLOAT,DOUBLE;INTEGERtoBIGINT,INT128,DOUBLE;BIGINTtoINT128;FLOATtoDOUBLE;DATEandTIMESTAMPto each other;TIMESTAMPtoTIME. NotBIGINTtoINTEGER, notDATEtoTIME, notDOUBLEtoFLOAT.NUMERICandDECIMALcount as the integer they are stored in:NUMERIC(1..4)is aSMALLINT,NUMERIC(5..9)andDECIMAL(1..9)anINTEGER, up to 18 digits aBIGINT, beyond that anINT128.A
NUMERIC/DECIMALthat has fewer digits before the decimal point (precision minus scale) than before. Lowering the scale alone, or raising precision, is accepted -- and so is raising the scale as long as the digits before the point do not shrink. (The maintainer's card expected a lowered scale to be refused; the server takes it.)A non-character type into a
CHAR/VARCHARtoo short for its text:SMALLINT6,INTEGER11,BIGINT20,INT12847 (one more for a numeric with a scale),FLOAT15,DOUBLE PRECISION24,DATE10,TIME13,TIMESTAMP25.Adding, removing or changing
IDENTITYon a column that exists:is_auto_incrementdiffers between the live and the target model. Nothing here writes an ALTER for it, and the server refuses an identity column added to a table that holds rows (measured on Firebird 5.0.4: "Cannot make field NOT NULL because there are NULLs present"). A new column that is an identity column is a plainADD ... GENERATED BY DEFAULT AS IDENTITY, which the server takes on an empty table; so is the column of a new table.Any type change of a column that is in the
PRIMARY KEY, in aUNIQUEconstraint or in aFOREIGN KEYof the source (live) model: the server answers Cannot update index segment used by an Integrity Constraint, even for a widening, even on an empty table. An ordinary index does not hold the type; its column is changed like any other. The membership is read from the model sections the diff already has (is_pkof the column,unique_constraints,foreign_keys), so a column in aUNIQUE INDEXthat is not a constraint is not a key here either.
A type or size the target does not prescribe is no opinion: a size the target leaves out is not compared (see changed_fields), and a rule that needs it does not fire. The default is different: the target of the diff is an introspected model, which carries default_value for every column and has undef where the column has none, so there undef means no default. A column that has a default in the database and none in the target gets ALTER ... DROP DEFAULT. A hand-made target model without the key default_value says nothing about it and leaves the default alone. A type this module has no rule for (DECFLOAT, the time zone types) is never refused.
There is no rebuild: nothing here adds a column, copies and renames. A refused change is something to do by hand -- or to give up -- and the error says which.
COLUMN CHANGES THAT CAN LOSE DATA
A type or size change the server carries out can still round, cut or rewrite the stored values. "lossy" says which ones do, so that "apply" in DBIO::Deploy::Base runs them only with allow_destructive (core ADR 0080). The answer is "column_change_lossy" in DBIO::Diff::Compare over the two columns with the type pairs of %LOSSLESS_PAIRS: the same type with a size at least as large (the scale of a NUMERIC / DECIMAL equal), or one of the pairs below. Every other change of type or size is lossy; in doubt it is.
smallinttointeger,bigint,int128;integertobigint,int128;biginttoint128: the integer types widen within the exact numbers and every value keeps its digits.smallintandintegertodouble precision: a double holds 53 bits exactly, so every 16 and 32 bit integer comes back as it went in.bigintandint128do not fit and stay lossy; so doesfloat, which holds 24 bits.datetotimestamp: the date is the timestamp at midnight.chartovarchar,binarytovarbinaryof the same length: the same bytes, padding included. With another length the server pads the stored value to the new one (char(5)'ab'becomes'ab'and seven blanks asvarchar(9)), so the value changes and the change is lossy; a smaller length is refused by the server besides.
Not lossless, whatever the server accepts: float to double precision (the decimal reading of a value changes), timestamp to date or time (the rest is cut), a number to a character type and the reverse (rewritten), a NUMERIC to a DECIMAL or the reverse, and a larger scale. t/99 holds the answer per pair; t/99-diff-column-lossless-live.t shows against a server that the values of every lossless pair read back unchanged.
A change of NOT NULL or of the default alone keeps every value and is not lossy; NOT NULL over a NULL is refused by "_refuse_unfillable" in DBIO::Firebird::Deploy. Operations that are not an alter are not lossy.
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.