NAME
DBIO::Firebird::Storage::InterBase - Driver for the Firebird RDBMS via DBD::InterBase
VERSION
version 0.900004
DESCRIPTION
This driver is a subclass of DBIO::Firebird::Storage::Common for use with DBD::InterBase, see that driver for general details.
Without "connect_call_use_softcommit" every commit on the connection closes the cursors still open on it, so a result set you are part-way through is cut off by the next statement that commits (see "CAVEATS"). disable_sth_caching is not needed for your code to function correctly: see "Statements the DBD destroys".
Every connection reads dates and timestamps in the ISO formats without being asked ("connect_call_datetime_setup"); DBIO::InflateColumn::DateTime support needs nothing more on the connection.
dbio_deploy_class
Answers DBIO::Firebird::Deploy, for this storage and for DBIO::Firebird::Storage, which inherits it. DBIO::Firebird::Storage::ODBC does not inherit this class and answers nothing of its own.
Statements the DBD destroys
DBD::Firebird keeps a list of the statements prepared on a connection and destroys all of them -- drops them on the server and frees what the handle binds and fetches with -- in two situations (ib_commit_transaction and ib_rollback_transaction in dbdimp.c of 1.39):
on the commit that follows DDL, whatever table the DDL was about, with or without
ib_softcommit. UnderAutoCommitthat is the DDL statement itself;on a rollback inside a transaction, unless
ib_softcommitis set -- everytxn_dothat dies.
The Perl handles are not told. They stay in DBI's statement cache, which this storage prepares through, and a handle taken from there afterwards does not fail: a SELECT executes and fetches nothing, with the warning no statement executing; an INSERT or UPDATE executes, answers undef (not an error: RaiseError stays silent) and writes nothing (measured, Firebird 5.0.4, DBD::Firebird 1.39); only INSERT ... RETURNING dies, with invalid statement handle.
So this storage asks every handle that comes out of the cache whether the DBD still has its statement -- a question the DBD answers without a round trip to the server -- and when it has not, empties the cache and prepares the statement anew. Reading and writing through DBIO, then changing the schema over the same connection (DBIO::Firebird::Deploy does) or rolling a transaction back, then reading and writing again works with statement caching on, which is the default.
What the storage cannot repair is a statement that was in use when it was destroyed: a cursor you are still fetching from goes with all the others, and the iteration ends there -- with that warning, and without an error.
A commit the server refuses
CREATE UNIQUE INDEX over duplicate rows is accepted as a statement and refused at the commit. DBD::Firebird fails that commit and leaves the handle in AutoCommit; DBI then dies with DBD driver has not implemented the AutoCommit attribute, and the server's text is only on $dbh->errstr. txn_commit raises that text. A commit that died of anything else keeps its own error. The handle stays usable. _use_transactional_ddl stays 0 ("Transactional DDL -- not claimed" in DBIO::Firebird::Deploy).
Query plans
"explain" in DBIO::ResultSet asks the storage for the whole operation of obtaining a query plan, through "_explain_query" in DBIO::Storage::DBI (ADR 0040; it replaced the string-valued _explain_sql). This engine is the one in the family with no statement to issue at all, and the override lives on this class -- inherited by DBIO::Firebird::Storage -- because it is a fact about these two DBDs rather than about the SQL:
there is no
EXPLAIN. The token appears nowhere in Appendix C of the Firebird 5.0 Language Reference, which lists every keyword reserved and non-reserved, so core's inheritedEXPLAIN <query>is aToken unknownparse error here rather than a wrong answer;the plan is an attribute of the prepared statement, read with the API call
isc_dsql_sql_info(isc_info_sql_get_plan). Nothing is executed to obtain it, which is why this driver prepares through "_prepare_rendered" in DBIO::Storage::DBI -- the prepare-without-execute seam core built for this engine (ADR 0040 section 6) -- and never reaches for$self->dbh. Going past the seam would cost_query_start/_query_endtracing and thereforeredact_bind_value(ADR 0025) over the explained query's ownWHERE-clause values,dbh_doretry over a dropped connection, DBIO::Storage::DBI::NoBindVars interpolation, and the read handler of a DBIO::Replicated::Storage;both DBDs expose that API call, and both expose it as a private statement-handle function:
$sth->func('ib_plan'). Verified in the sources --Firebird.xsof DBD::Firebird 1.39 andInterBase.xsof DBD::InterBase 0.48 carry the same XSUB, DBD::Firebird being a fork of DBD::InterBase. It is not the attribute$sth->{ib_plan}that ADR 0040 section 6 and core's_explain_query/_prepare_renderedPOD spell: neither DBD has anib_plankey in itsdbd_st_FETCH_attrib, so that spelling asks DBI for an unrecognised attribute and getsundef.
The plan is one opaque string, so it comes back as a single row of a single column, [ [ $plan ] ], rather than as invented columns -- ADR 0040 section 2: the return type is rows and their contents were never portable.
Two caveats, both from the XS rather than from a server:
what you get is Firebird's legacy plan (
PLAN (FOO NATURAL)), with a trailing newline the XS appends. The richerexplained planof Firebird 3 and up,isc_info_sql_explain_plan, is not reachable: neither DBD mentions it;both XSUBs read the plan into a fixed 2048-byte buffer (
PLAN_BUFFER_LEN) and answerundefwhen the plan does not fit. This driver throws on thatundefinstead of handing back a row containing nothing, since an empty plan cell is indistinguishable from a query that has no plan.
ib_ attributes through an AccessBroker
A storage connecting through a DBIO::AccessBroker refuses a lowercase key it would not apply -- in the broker's hash answer or beside the broker -- unless the key starts with a prefix the storage declares for the DBD its DSN names ("_dbd_attribute_prefixes" in DBIO::Storage::DBI). Core asks DBI's registry of driver prefixes, which knows ib_ for DBD::InterBase and has no entry for DBD::Firebird, although that DBD, a fork of the other, takes its attributes under the same prefix. This class declares it: asked about the DBD Firebird it answers ib_, and leaves every other DBD to core. So
$schema->connect($broker, { ib_softcommit => 1 });
and a broker answering { dbname => ..., ib_softcommit => 1 } connect over dbi:Firebird: as they do over dbi:InterBase:.
The declaration is on this class, and so inherited by DBIO::Firebird::Storage, because this is where the ib_* API is spoken; DBIO::Firebird::Storage::ODBC does not inherit it, and an ib_ key on a dbi:ODBC: connection is refused.
The prefix says less than it seems to. It tells the storage that the key is addressed to this DBD, not that the DBD reads it from where a broker's answer puts it. That place is the attribute hash of DBI->connect, and DBD::Firebird 1.39 (dbdimp.c) takes from it only ib_softcommit, ib_enable_utf8, ib_time_all, ib_dateformat, ib_timeformat, ib_timestampformat and ib_db_triggers. ib_dialect, ib_charset, ib_role, ib_cache and ib_dbkey_scope are read from the DSN alone: as attributes they pass this check, and DBI then drops them without a word -- the connection is opened without them. Give those inside the DSN, which a broker does by answering the DBI form [ 'dbi:Firebird:dbname=...;ib_charset=UTF8', $user, $password ].
SQL dialect 3 through an AccessBroker
The storage forces SQL dialect 3 (quoted identifiers, the modern types) by adding ib_dialect=3 to the DSN, because DBD::InterBase takes dialect 1 otherwise and both DBDs read ib_dialect from the DSN alone. With an DBIO::AccessBroker the DSN is the broker's answer, asked again for every connection, so the dialect joins each answer that is a DSN of Firebird or InterBase: a DBI-form answer, a hash carrying a dsn, and the connection parameters of DBIO::AccessBroker::Static alike. The connection is opened once, with it.
An answer that names ib_dialect itself is checked -- the key in any letter case and with blanks around it, in the DSN or in the attribute hash -- and only 3 stands: such a connection would run every statement in a language other than the one this storage speaks, so an answer naming another dialect is refused with an exception that names the place (the DSN, the attributes) and never a value of the DSN or the password. An ib_dialect attribute of 3 alone is not enough, since the DBD reads the DSN only and drops the attribute: the DSN gets ib_dialect=3 then too. The same holds for a DSN spelling the key as the DBD does not read it (IB_DIALECT=3, ib_dialect = 3: its parser compares the key exactly and trims nothing): ;ib_dialect=3 is appended unless the DBD itself reads a 3. (That DBD::InterBase takes dialect 1 without it is taken from that DBD's documentation, not measured here; t/22-broker-live.t measures that a plain connection in dialect 1 reads "RDB$DATABASE" as a string literal.) What this cannot reach is a connection that code opens -- a coderef or dbh_maker answer: there the dialect is the code's to set, as it is without a broker.
Text is UTF8 on every native connection
Over DBD::Firebird the storage adds ib_charset=UTF8 to the DSN and ib_enable_utf8 => 1 to the attributes (with an AccessBroker to every answer, as the dialect). Without them DBD::Firebird 1.39 stores the bytes Perl holds and reads bytes back: a string with a character above U+00FF is stored right and comes back as its UTF-8 bytes, and "\x{fc}", an unflagged string, is a malformed UTF-8 string to a UTF8 column. Measured on Firebird 5.0.4 against a database of character set UTF8 and one of NONE: with both settings every text round-trips as the characters written, in either, so the database's character set needs no change. The DBD encodes only strings Perl flags as UTF-8, which is why the storage flags a text that has a character above 0x7F when it binds (not the bytes of a binary BLOB, binary, varbinary), in _adjust_bind_values, core's one seam for both the single and the bulk path of populate.
What the connect info says stays: a DSN that names an ib_charset, whatever its value, and an ib_enable_utf8 attribute, 0 included, switch the default off for what they name (ib_enable_utf8 is set only with a UTF8 charset, the one the DBD decodes). A DBD::InterBase DSN and the ODBC storage are not touched. A text column of an existing database holding bytes in another encoding (character set NONE, written by a connection that had none) is read as UTF-8 through a default connection; give that connection ib_enable_utf8 = 0> or its own ib_charset.
A BLOB compared with a bound value
DBD::Firebird refuses a BLOB parameter in a SELECT (BLOB as an input param for SELECT is not allowed), and a text or binary BLOB column is what text, clob, blob are written as, so search({ v => 'x' }) would die in the DBD. Firebird itself compares a BLOB with a VARCHAR, so the storage writes the placeholder of such a bound value in a SELECT as CAST(? AS VARCHAR(8191) CHARACTER SET UTF8) (a text BLOB) or CAST(? AS VARCHAR(32765) CHARACTER SET OCTETS) (a binary one), measured on Firebird 5.0.4 with a UTF8 and a NONE database. The limits are those of VARCHAR (32 765 bytes, Firebird Language Reference, Character data types): a value above them cannot be compared, and the storage says so before the DBD does -- a DBIO::Exception that refuses, naming table and column. UPDATE and DELETE are not rewritten: the DBD takes a BLOB parameter there. The cast assumes the connection speaks UTF8, the default above; with another ib_charset a non-ASCII value in such a comparison is not supported.
A HostBound view over a DSN of this driver
DBIO::AccessBroker::HostBound (DBIO::Replicated: one host per replicant, one credential) binds its host into a broker's DSN through the storage's "_bind_host_into_dsn" in DBIO::Storage::DBI. Core's default knows host= and port= (it also declines hostname=) and declines db=host:path, while db=/path;host=... and a local name it translates; this class translates the forms the DBDs take:
db=host[/port]:path(alsodbname=,database=): the prefix of the database name is replaced;host=h[/port]orhostname=h, with the port inside the host or inport=: replaced where it stands. A view port goes toport=; a view without a port leaves the answer's;a local database name:
host=(andport=) are put in front.
It declines, naming the form and never a value of the DSN, a part that is not key=value, a host named both in the database name and in host=, more than one database or host key, a URL-style database name (inet://...), a service name where a port stands (db=host/service:path, and host=h/service when the view brings no port to take its place: the service would drop out of the DSN without a word), an IPv6 database name (db=[::1]:path), a drive-relative path (db=C:data.fdb, neither a host nor an absolute local path), a view host with a colon, slash or space in it, and a DSN of any other DBD. The ODBC storage declines every DSN: its driver takes the server inside DBNAME=host/port:path.
METHODS
connect_call_use_softcommit
Used as:
on_connect_call => 'use_softcommit'
In connect_info to set the DBD::InterBase ib_softcommit option.
With it DBD::Firebird commits and rolls back retaining, and a cursor that still has rows to fetch stays open across the commits of other statements on the connection. Without it the first such commit closes it, and the next fetch dies with Cursor is not open; disable_sth_caching => 1 does not change that.
Neither setting keeps a cursor open across the commit that follows DDL: see "Statements the DBD destroys".
The downside of using this option is that your process will NOT see UPDATEs, INSERTs and DELETEs from other processes for already open statements.
connect_call_datetime_setup
Used as:
on_connect_call => 'datetime_setup'
In connect_info to set the date and timestamp formats using:
$dbh->{ib_time_all} = 'ISO';
Every connection of this storage does that by itself, before the caller's on_connect_* actions run: without it DBD::Firebird reads a timestamp back in the locale's %c format ("Mon 19 Oct 2026 04:43:18 AM CET"), which the parser of this driver does not expect. This call does the same and is harmless twice; an action of your own that sets another ib_time_all comes after the default and wins.
See DBD::InterBase for more details.
The TIMESTAMP data type supports up to 4 digits after the decimal point for second precision. The full precision is used.
The DATE data type stores the date portion only, and it MUST be declared with:
data_type => 'date'
in your Result class.
Timestamp columns can be declared with either datetime or timestamp.
Datetime inflation is handled by DBIO::Firebird::DateTime::Format (a DBIO::Storage::DateTimeFormat subclass backed by DateTime::Format::Strptime), which loads DateTime::Format::Strptime when a datetime column is first inflated. It is a recommended, not a required, dependency of this distribution: install it to use datetime columns.
For DBIO::Firebird::Storage::ODBC, this is a noop.
CAVEATS
with "connect_call_use_softcommit", you will not be able to see changes made to data in other processes. If this is an issue, do without it and do not keep a cursor open across another statement's commit: fetch the rows first. disable_sth_caching is no workaround for that: a cursor is closed by the commit whether or not its statement came out of the cache.
a cursor that is still open does not survive the commit that follows DDL on the same connection, nor, without "connect_call_use_softcommit", a rollback inside a transaction. See "Statements the DBD destroys".
SEE ALSO
DBIO::Firebird - Firebird schema component
DBIO::Firebird::Storage - Firebird driver via DBD::Firebird
DBIO::Firebird::Storage::Common - Shared Firebird/InterBase logic
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.