Skip to content

Commit 22618ec

Browse files
committed
fix: bigint IN/NOT_IN precision loss for values > 2^53
Number(bigint) coercion in matchesFilter lost precision for int64 column values exceeding Number.MAX_SAFE_INTEGER, producing wrong filter results. Move cross-type equivalents into getInSet() at cache-build time — integer numbers get BigInt copies, safe-range bigints get Number copies — so set.has() works directly without any unsafe coercion on the hot path.
1 parent 38ad04a commit 22618ec

1 file changed

Lines changed: 14 additions & 11 deletions

File tree

src/decode.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -494,20 +494,11 @@ export function matchesFilter(
494494
case "lte": { const [a, b] = coerceCompare(val, t); return (a as number | bigint | string) <= (b as number | bigint | string); }
495495
case "in": {
496496
if (!Array.isArray(t)) return false;
497-
const set = getInSet(t);
498-
if (set.has(val as number | bigint | string)) return true;
499-
// bigint/number cross-type: 5n !== 5 for Set.has, so coerce
500-
if (typeof val === "bigint") return set.has(Number(val));
501-
if (typeof val === "number" && Number.isFinite(val) && Number.isInteger(val)) return set.has(BigInt(val));
502-
return false;
497+
return getInSet(t).has(val as number | bigint | string);
503498
}
504499
case "not_in": {
505500
if (!Array.isArray(t)) return false;
506-
const set = getInSet(t);
507-
if (set.has(val as number | bigint | string)) return false;
508-
if (typeof val === "bigint") return !set.has(Number(val));
509-
if (typeof val === "number" && Number.isFinite(val) && Number.isInteger(val)) return !set.has(BigInt(val));
510-
return true;
501+
return !getInSet(t).has(val as number | bigint | string);
511502
}
512503
case "between": {
513504
if (!Array.isArray(t) || t.length !== 2) return false;
@@ -542,6 +533,18 @@ function getInSet(values: readonly (number | bigint | string)[]): Set<number | b
542533
let cached = inSetCache.get(values);
543534
if (cached) return cached;
544535
const set = new Set<number | bigint | string>(values);
536+
// Add cross-type equivalents so set.has() works for both bigint and number
537+
// representations without unsafe Number(bigint) coercion that loses precision.
538+
for (const v of values) {
539+
if (typeof v === "number" && Number.isFinite(v) && Number.isInteger(v)) {
540+
set.add(BigInt(v));
541+
} else if (typeof v === "bigint") {
542+
// Only add Number equivalent if the bigint fits safely
543+
if (v >= -9007199254740991n && v <= 9007199254740991n) {
544+
set.add(Number(v));
545+
}
546+
}
547+
}
545548
inSetCache.set(values, set);
546549
return set;
547550
}

0 commit comments

Comments
 (0)