Repository navigation
v2.16.1
Patch release: correctness fixes for fixed-point and INT128 values, on both the parameter and the result side. Negative INT128 parameters now work at all, batch numeric parameters keep every digit the metadata declares, and scales past 15 no longer decode as NaN. No API changes; drop-in upgrade from 2.16.0.
Fixes
- Scaled numeric batch parameters keep their precision (#433). The Firebird 4+ batch API requires every message to match the statement's described input format exactly, so the driver — not the server — converts each value to the integer coefficient the column declares. That conversion went through
parseFloatandMath.round(n * 10^-scale), which was wrong in four separate ways: exact decimal strings were forced through binary floating point and lost digits; decimal ties rounded down because of the binary representation (2.675at scale 2 stored2.67); negative ties rounded asymmetrically; and malformed numeric strings were partially accepted, withparseFloatsilently reading1oopsas1. Values are now parsed into an exact scaled integer coefficient without passing strings or BigInts throughNumber, and discarded digits round to nearest with ties away from zero, matching Firebird's own conversion of decimal parameter text.2.675at scale 2 stores2.68,-2.675stores-2.68,1.00105at scale 4 stores1.0011.- Exact strings beyond the safe integer range, such as
'900719925474.0992', retain every digit. NUMERIC/DECIMAL,BIGINTandINT128batch parameters acceptnumber, decimalstringandbigint; use a string or BigInt when the value is outside JavaScript's safe integer range.
- Batch numeric values are range-checked before any packet is sent. SMALLINT, INTEGER, INT64 and INT128 parameters are validated against the physical signed range of the target width, so an out-of-range or malformed value fails with a
column N (FIELD_NAME)message and leaves the connection usable, instead of writing a truncated or wrapped value onto the wire. - Batch numerics are validated before the BLOB pre-pass.
executeBatchuploads Buffer/string BLOB values as transaction blobs before building the messages. Validating numerics only at encoding time meant a batch that could never be sent had already written blob state into the transaction. The check now runs first, so nothing is uploaded for a batch that will be rejected. - Negative INT128 parameters are encoded correctly. The high word of the 128-bit value was written with
writeBigUInt64BE, which cannot represent the negative high word of a negative value. It is now written as signed two's complement, so negativeBigIntparameters round-trip through both the batch path and ordinaryquery/execute. - Negative INT128 results are decoded correctly. The default (
LOSSY) numeric mode read the coefficient through the unsigned reader, so every negative INT128 came back as a huge positive number or string. It now uses the signed reader in every numeric mode, asSAFEandSTRINGalready did. - Unsafe negative INT128 results stay exact. With the signed reader in place, the unsafe-magnitude test in the lossy path — which checked only the upper bound — became reachable from below, so a large negative fell through to
Number()and lost precision while its positive twin was returned as an exact string. The check is now two-sided, matching the exact-mode decoder.- Before:
-1.7014118346046923e+38. After:'-170141183460469231731687303715884105728', symmetric with the positive extreme.
- Before:
- Scales beyond 15 no longer decode as
NaN. Decoding applied the scale through a lookup table of divisors that stopped at10^15; any larger scale indexed past the end of the array and producedNaN. That is reachable for INT64-backed columns (NUMERIC(18,18)) and routine for INT128, whose scale runs to 38. The table is replaced by a helper built onMath.pow(10, n), which returns the identical double for every exponent the table did cover.NUMERIC(38,18)coefficient12345decodes as1.2345e-14instead ofNaN.
Behaviour changes
All three are bug fixes, but they change observable behaviour and are worth reading before upgrading:
- Batch fixed-point parameters now reject booleans,
Dateobjects and plain objects, which previously coerced throughNumber()— usually to a value nobody intended. Pass a number, decimal string or BigInt. - A malformed numeric string in a batch now throws instead of being partially parsed. Code that relied on
'12abc'inserting12will see an error. - Positive scales now multiply rather than divide when decoding. A Firebird coefficient represents
value * 10^scale, which the exact-mode decoder and the string formatter already assumed; the lossy table path divided by10^|scale|and so was off by10^(2*scale). This affects only the rare positive-scale columns produced by dialect 1 and some legacy metadata.
Documentation
The README's numericMode section previously described the INT128 lossy path only as "a mixed number/string path" whose result type "can depend on the Firebird wire type and value" — wording that was accurate only because the behaviour was asymmetric. It now states the rule directly: a coefficient inside the inclusive safe range decodes to a scaled number, one outside it in either direction to an exact scaled string, with a worked example at both INT128 extrema. A new paragraph records that every declared scale is supported, including NUMERIC(18,18) on INT64 and the 38 digits an INT128 column can declare, and that positive scales multiply. The batch section documents the accepted input forms and the precision limitation of unsafe JavaScript numbers.
Tests
- Unit coverage for the new coefficient converter: canonical-decimal rounding with ties away from zero, exact string and BigInt inputs, positive scales, signed-zero normalisation, syntax and finiteness validation, exponent bounds, and the signed range of all four widths including the INT128 extrema.
- INT128 extrema formatted symmetrically in lossy mode; scales of 18 and 38 decoding to finite numbers on both INT64 and INT128; positive scales multiplying across
SQLVarInt,SQLVarInt64andSQLVarInt128. - Firebird 4+ integration tests compare batch results against ordinary
op_execute— the reference for Firebird's own decimal conversion — and cover exact unsafe scaled strings, INT64 minima and maxima, signed INT128 round trips through both the regular and batch paths, and the BLOB/numeric validation ordering (asserting that no blob is uploaded when a numeric is invalid). - Full unit suite: 795 tests across 58 files.
Upgrading
Drop-in replacement for 2.16.0 unless you were passing booleans, Dates or malformed numeric strings as batch fixed-point parameters, or reading positive-scale columns. Applications that worked around negative INT128 values — by casting to VARCHAR, by switching to numericMode: 'string', or by flipping the sign client-side — can drop those workarounds.
Thanks to @Gobbo89 for #433, which carried the batch conversion rewrite and the signed INT128 wire handling.