Skip to content

Prepared statements & the statement cache ​

Every connection keeps an LRU cache of prepared statements keyed by SQL text (statementCacheSize, default 64; 0 disables). Measured round trips, asserted by integration tests on FB 3/4/5/6:

Operation (inside an open transaction)Round trips
cold query (prepare → rows)2
warm query, one batch1 (execute + fetch coalesced)
warm DML including affected count1 (execute + counts coalesced)

For hot paths you can also pin a statement explicitly:

ts
await using stmt = await db.prepare('select name from users where id = ?');
const rows = await stmt.query([42], tx);       // 1 round trip per execution
const one  = await stmt.queryOne([42], tx);

Prepared statements also expose executeBatch — repeat batches skip the prepare round trip entirely:

ts
const stmt = await db.prepare('insert into t (a, b) values (?, ?)');
await stmt.executeBatch(rowsA);
await stmt.executeBatch(rowsB);
await stmt.close();

Metadata-lock caveat (standard Firebird behavior)

A live prepared statement — cached or pinned — makes its attachment refuse to yield the relation locks of the tables it references, so DDL on those tables from other connections blocks for its whole lock wait and fails with object … is in use. (With the driver's default 10 s lock wait that's a clear error; under Firebird's native unbounded wait it reads as a hang.) Freeing the statements flips the attachment to yielding on demand: a waiting DDL then proceeds in milliseconds — though a wait: false DDL never asks the holder to yield and may still see object … is in use.

What releases the handles:

  • DDL executed through the same connection needs nothing — the driver clears that connection's cache automatically after any DDL it runs, and an attachment's own statements never block its own DDL.
  • db.clearStatementCache() / pool.clearStatementCaches() release them explicitly before external migrations.
  • createPool({ statementCache: 'clear-on-release' }) frees them at every pool release — see Connection pooling.
  • Disconnecting releases everything unconditionally (the pool's idleTimeoutMs eviction bounds pin lifetime on idle members).

MIT Licensed. Contains data derived from the Firebird project under IDPL/IPL.