NAME
DBIO::Firebird::Storage::Common - Shared Firebird / InterBase storage logic
VERSION
version 0.900004
DESCRIPTION
This class contains the shared logic for both Firebird and InterBase drivers. It extends DBIO::Storage::DBI directly to avoid unnecessary inheritance depth.
Existence queries
"results_exist" in DBIO::ResultSet and its two siblings work here because this class declares the supports_exists_query capability (ADR 0037). Core defaults that capability to 0 and throws on all three methods without it, rather than degrading silently to "has_rows" in DBIO::ResultSet -- which stays the portable alternative for callers that must also run against a driver which has not declared it.
Declaring it alone would not be enough: core's base rendering of "_exists_value_sql" in DBIO::Storage::DBI is SELECT CASE WHEN EXISTS <inner> THEN 1 ELSE 0 END, a SELECT with no FROM clause, and Firebird has no FROM-less SELECT at all. The override appends FROM RDB$DATABASE -- the one-row system table every Firebird and InterBase database has, and the same one this class already selects from for GEN_ID and the engine version -- and leaves the CASE spelling to core, so there is one copy of it.
Set operations: UNION only
Firebird has no set difference and no intersection. UNION and UNION ALL are the only set operators in the language, so four of the six DBIO::ResultSet set operations -- intersect, intersect_all, except and except_all -- are refused from "_set_operation_keyword" in DBIO::Storage::DBI with a typed exception, at query-build time, before any operand SQL is rendered (ADR 0039). Before that refusal existed they emitted keywords the engine has no token for and failed as a syntax error several layers below the method the caller invoked.
Sourced rather than assumed:
the
<query-expression>production in the Firebird 5.0 Language Reference is<query-term> | <query-expression> UNION [{ DISTINCT | ALL }] <query-term>-- one set operator, no alternatives;Appendix C lists every Firebird 5.0 keyword, reserved and non-reserved, and contains
UNIONbut neitherEXCEPTnorINTERSECTnorMINUS. The tokens are not in the grammar at all, so this is not a missing feature behind a compatibility switch;https://github.com/FirebirdSQL/firebird/issues/1194 (tracker CORE808) has requested
INTERSECTandEXCEPTsince 2004 and is still open, unmilestoned and without a linked pull request;the InterBase 2020 Language Reference documents
[UNION [ALL] select_expr]and nothing else, which matters because DBIO::Firebird::Storage::InterBase inherits this refusal.
The refusal is unconditional and is not a version gate. ADR 0039 section 7 allows either an unconditional refusal or a class-level opt-in for a deployment that knows its server, and forbids probing the server, because "_set_operation" in DBIO::ResultSet bakes the keyword into a literal from clause at construction time. Unconditional is the honest choice here and it costs nothing, unlike Oracle's version-bounded MINUS ALL gap: no released Firebird or InterBase has ever had these operators, so there is no version being withheld from. A class-level opt-in would be an opt-in for a server that does not exist, and would let a deployment switch on SQL no engine parses. If issue 1194 ever ships, the gap becomes version-bounded and the opt-in becomes the right shape -- that is the falsification condition, not a promise.
Random ordering
"rand" in DBIO::ResultSet orders by whatever "_random_function" in DBIO::Storage::DBI names. Core answers RANDOM(), right on PostgreSQL and SQLite and a function neither Firebird nor InterBase has at all; the spelling here is RAND(). It is overridden on this class rather than on either driver because it is a fact about the SQL both engines speak, not about either DBD.
Sourced rather than assumed:
RAND()is an internal function of Firebird since 2.1 (Language Reference Update 2.1, Internal functions; unchanged in the Firebird 5.0 Language Reference, Mathematical Functions), returning aDOUBLE PRECISIONbetween 0 and 1. Appendix C of the 5.0 reference lists every keyword, reserved and non-reserved, and contains noRANDOMin any form;before 2.1, and on InterBase, the same call is the
ib_udfexternal function, declaredDECLARE EXTERNAL FUNCTION rand RETURNS DOUBLE PRECISION BY VALUE ENTRY_POINT 'IB_UDF_rand' MODULE_NAME 'ib_udf'. The name isRANDeither way, which is why one override covers both engines and every version; a database that declares the UDF shadows the internal function of the same name (2.1 Language Reference Update), and the emitted SQL does not care which of the two resolves it;the value is re-evaluated per row, which is the half worth checking rather than assuming: an engine that computes it once per statement gives every row the same sort key and answers with the rows unshuffled, in their natural order, with no error to notice. Firebird does not do that. Dmitry Yemanov on tracker CORE4362 (https://github.com/FirebirdSQL/firebird/issues/4684), where a user reports the opposite complaint -- that
rand()-derived values differ on every reference -- answers: "This is an implementation detail. Firebird does not materialize simple expressions." The one release where the optimizer did fold such an expression away, 5.0 Beta 1 and RC 1, was treated as a bug and fixed in 5.0 RC 2 (FirebirdSQL/firebird#7853, Do not consider non-deterministic expressions as invariants in pre-filters) -- and even there the folding was inWHEREpre-filters, not in the sort key "rand" in DBIO::ResultSet writes.
Unverified: no server was run for any of this. The documentation above is the whole evidence, both for RAND() being the spelling and for its per-row evaluation; the live half of t/58-explain-query.t is where a server checks them.
Transactional DDL
This class declares _use_transactional_ddl(0), and that 0 is not a statement about the engine. Firebird does have transactional DDL: metadata lives in ordinary MVCC records in the rdb$ system tables, a DDL statement stays in the user transaction until it ends, and a ROLLBACK takes it back -- which is why isql carries SET AUTODDL at all, and why DBIO::Deploy::Base lists this engine in its transactional set.
What the declaration withholds is the narrower promise "_execute_ddl" in DBIO::Deploy::Base acts on: that this driver's whole DDL batch may be bracketed in one $storage->txn_do and be atomic. Three things stand between the engine fact and that promise -- a same-name DROP INDEX / CREATE INDEX pair in the diff, CREATE INDEX doing its work at COMMIT, and ib_softcommit turning both the commit and the rollback into retaining calls -- and none of them could be settled without a server. The full reasoning, its sources and the condition that would flip the value are in "Transactional DDL -- not claimed" in DBIO::Firebird::Deploy and in the comment on the declaration itself.
All three transports inherit it. t/42-deploy-transactional-ddl.t pins the value, pins that this class declared it rather than inheriting core's identical default, and drives the counterfactual through _execute_ddl in both directions.
METHODS
sqlt_type
Returns Firebird, identifying this storage to SQL::Translator.
CAVEATS
last_insert_idsupport by default only works for Firebird versions 2 or greater, auto_nextval however should work with earlier versions.
Values in error text
Core sends every text an engine writes into an error through "_mask_error_values" in DBIO::Storage::DBI. The Firebird server quotes values in two sentences -- Problematic key value is ("ID" = 7, "EMAIL" = 'x') of a PRIMARY KEY, UNIQUE or FOREIGN KEY violation and conversion error from string "x" -- also when the value was bound and is only a character long. This class applies "mask_error_details" in DBIO::Firebird::Util first and then core's rule for the bound values, on all three transports. The type, the column, the constraint, the table and the error code stay in the message; the errstr of the handle is the DBI attribute and stays as the server wrote it.
SEE ALSO
DBIO::Firebird - Firebird schema component
DBIO::Firebird::Storage - Firebird driver via DBD::Firebird
DBIO::Firebird::Storage::InterBase - InterBase driver
DBIO::Firebird::Storage::ODBC - Firebird via DBD::ODBC
DBIO::Storage::DBI - Base DBI storage class
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.