Skip to content

Commit aec0ced

Browse files
committed
fix: LIKE case sensitivity, bigint arithmetic precision, CAST(str AS BIGINT)
Three SQL correctness fixes: 1. decode.ts: compileLikeRegex used "i" flag — LIKE was case-insensitive, violating SQL standard and disagreeing with the WASM SIMD path (which does byte-level comparison). Removed "i" flag, kept "s" for dotAll. 2. evaluator.ts: arithmetic ops (add/subtract/multiply) went through toNumber() which loses precision for bigint values > MAX_SAFE_INTEGER. Added numericOp() that preserves bigint arithmetic when both operands are bigint or safe integers. Unary minus also preserves bigint. 3. evaluator.ts: CAST(string AS BIGINT) went through toNumber() → float before BigInt(), losing precision for large integer strings. Now uses BigInt(val) directly for string inputs.
1 parent 770508a commit aec0ced

File tree

2 files changed

+24
-6
lines changed

2 files changed

+24
-6
lines changed

src/decode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ export function compileLikeRegex(pattern: string): RegExp {
584584
if (cached) return cached;
585585
// Escape regex metacharacters, then replace SQL wildcards
586586
const escaped = pattern.replace(/[.+?^${}()|[\]\\*]/g, "\\$&");
587-
const re = new RegExp("^" + escaped.replace(/%/g, ".*").replace(/_/g, ".") + "$", "i");
587+
const re = new RegExp("^" + escaped.replace(/%/g, ".*").replace(/_/g, ".") + "$", "s");
588588
likeRegexCache.set(pattern, re);
589589
if (likeRegexCache.size > 1000) likeRegexCache.clear(); // prevent unbounded growth
590590
return re;

src/sql/evaluator.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ function evaluateBinary(op: string, leftExpr: SqlExpr, rightExpr: SqlExpr, row:
118118
case "le": return compare(left, right) <= 0;
119119
case "gt": return compare(left, right) > 0;
120120
case "ge": return compare(left, right) >= 0;
121-
case "add": return toNumber(left) + toNumber(right);
122-
case "subtract": return toNumber(left) - toNumber(right);
123-
case "multiply": return toNumber(left) * toNumber(right);
121+
case "add": return numericOp(left, right, (a, b) => a + b, (a, b) => a + b);
122+
case "subtract": return numericOp(left, right, (a, b) => a - b, (a, b) => a - b);
123+
case "multiply": return numericOp(left, right, (a, b) => a * b, (a, b) => a * b);
124124
case "divide": {
125125
const divisor = toNumber(right);
126126
return divisor === 0 ? null : toNumber(left) / divisor;
@@ -149,7 +149,7 @@ function evaluateUnary(op: string, operandExpr: SqlExpr, row: Row): unknown {
149149
}
150150
if (op === "minus") {
151151
if (val === null) return null;
152-
return -toNumber(val);
152+
return typeof val === "bigint" ? -val : -toNumber(val);
153153
}
154154
return null;
155155
}
@@ -183,14 +183,32 @@ function toNumber(val: unknown): number {
183183
return 0;
184184
}
185185

186+
/** Preserve bigint precision when both operands are bigint or safe integers. */
187+
function numericOp(
188+
left: unknown, right: unknown,
189+
numFn: (a: number, b: number) => number,
190+
bigFn: (a: bigint, b: bigint) => bigint,
191+
): number | bigint {
192+
const lb = typeof left === "bigint";
193+
const rb = typeof right === "bigint";
194+
if (lb && rb) return bigFn(left as bigint, right as bigint);
195+
if (lb && typeof right === "number" && Number.isInteger(right)) return bigFn(left as bigint, BigInt(right));
196+
if (rb && typeof left === "number" && Number.isInteger(left)) return bigFn(BigInt(left), right as bigint);
197+
return numFn(toNumber(left), toNumber(right));
198+
}
199+
186200
function matchLike(value: string, pattern: string): boolean {
187201
return compileLikeRegex(pattern).test(value);
188202
}
189203

190204
function castValue(val: unknown, targetType: string): unknown {
191205
if (val === null) return null;
192206
const t = targetType.toLowerCase();
193-
if (t === "bigint") return typeof val === "bigint" ? val : BigInt(Math.trunc(toNumber(val)));
207+
if (t === "bigint") {
208+
if (typeof val === "bigint") return val;
209+
if (typeof val === "string") { try { return BigInt(val); } catch { return 0n; } }
210+
return BigInt(Math.trunc(toNumber(val)));
211+
}
194212
if (t === "int" || t === "integer") return Math.trunc(toNumber(val));
195213
if (t === "float" || t === "double" || t === "real" || t === "decimal" || t === "numeric") return toNumber(val);
196214
if (t === "text" || t === "varchar" || t === "string" || t === "char") return String(val);

0 commit comments

Comments
 (0)