Repository navigation
v2.19.0
Minor release that adds a configurable fetchSize option (connection-level, URI query parameter, and per-query) and fixes two bugs in the result-fetch and response-delivery paths:
- Stack overflow in
fetchAllwith large batches:doSynchronousLoopnow runs as an iterative trampoline instead of recursing on every synchronousnext()call. - Swallowed
RangeErrorfrom user callbacks:decodeResponseand the socket'data'handler no longer mistake aRangeErrorthrown by consumer callback code for a truncated wire packet. - New
fetchSizeoption (#313): Configure how many rows are requested perop_fetchround-trip (default200, max65535).
The default behavior (fetchSize: 200) is unchanged.
Key Changes
1. Configurable fetchSize option (#313, #443)
Rows were previously always fetched 200 per op_fetch, with each batch requiring a network round-trip. On high-latency links, large result sets spent most of their time waiting for the next batch (e.g., fetching 20,000 rows over a 50 ms RTT link drops from 5.35 s at 200 to 0.60 s at 5000).
fetchSize can be configured in three ways:
- Connection option:
options.fetchSize = 5000 - Connection URI:
firebird://localhost/employee?fetchSize=5000 - Per-query option:
db.query(sql, params, cb, { fetchSize: 5000 })
It applies to query, execute, sequentially, queryStream, and connection.fetch() when no explicit count is passed. Non-positive or non-integer values fall back to 200, and values above 65535 are clamped to 65535 (p_sqldata_messages is an unsigned 16-bit wire field).
2. Iterative trampoline in doSynchronousLoop (#443)
fetchAll hands each fetched batch to doSynchronousLoop, which previously invoked the per-row callback recursively. With batches of a few thousand rows (or a deep per-row consumer), synchronous next() calls grew the call stack until RangeError: Maximum call stack size exceeded was thrown.
doSynchronousLoop now runs as a while loop trampoline:
- A synchronous
next()marks the current row as handled and advances the loop in constant stack space. - An asynchronous
next()exits the loop and resumes whennext()is called. - Each row receives its own guarded
next()so duplicate or stale calls cannot skip rows or restart the loop after an error.
3. Consumer RangeError isolation in decodeResponse and socket 'data' handler (#443)
Previously, decodeResponse and the socket 'data' handler treated any RangeError as an incomplete packet ("wait for more data"). Because the result callback chain runs synchronously inside both, a RangeError thrown by user code (such as new Date(NaN).toISOString()) was silently swallowed, and the already-consumed bytes were re-queued for decoding on the next 'data' event—corrupting protocol state or hanging the query.
decodeResponsenow tracks whether the response has been delivered tocband only treats aRangeErroras an incomplete packet prior to delivery.- The socket
'data'handler catches exceptions thrown by the dequeued response callback, continues decoding any remaining responses in the current buffer, and rethrows the callback error viaprocess.nextTick.
Pull Requests
- #443
fix(fetch): stack overflow and swallowed RangeErrors in fetchAll; add fetchSize option— @JohnnyBill - #442
chore(deps-dev): bump source-map-js from 1.2.1 to 1.2.2— @dependabot
Installation
npm install node-firebird@2.19.0Full Changelog: v2.18.2...v2.19.0