Repository navigation
drizzle: this.stmt.raw().all(...) from inside PreparedQuery.values() returns (number).all #645
Description
Summary
Followup to #643 (partially closed in v0.5.764). The dispatch tower path emitted for class-method-internal calls of the shape obj.method().method2(...) — where obj is a non-class native handle (small int wrapped as POINTER NaN-box) — returns the raw int handle from .method() directly to .method2() instead of NaN-boxing it as a POINTER. The chained .method2() then sees a number receiver and throws.
Repro
Direct call site at the entry module works in v0.5.764:
import Database from "better-sqlite3";
const sqlite = new Database(":memory:");
sqlite.exec(`CREATE TABLE t (id INTEGER, name TEXT)`);
sqlite.exec(`INSERT INTO t VALUES (1, 'a')`);
const stmt = sqlite.prepare("SELECT * FROM t");
const rs: any = (stmt as any).raw();
console.log("typeof rs=", typeof rs); // object ✓
console.log("rs.all()=", JSON.stringify(rs.all())); // [[1,"a"]] ✓Same chain from inside a class method fails:
// In drizzle-orm/better-sqlite3/session.js (compilePackages):
class PreparedQuery {
constructor(stmt, ...) { this.stmt = stmt; ... }
values(placeholderValues) {
return this.stmt.raw().all(...params); // throws (number).all is not a function
}
}The instrumented trace (console.log inside values()) shows:
[values] this.stmt typeof= object typeof this.stmt.raw= undefined
[values] typeof raw= number typeof raw.all= number
TypeError: (number).all is not a function
So this.stmt.raw() returned a number, and .all() on that number throws.
Why the direct-call shape works
v0.5.764's perry-stdlib dispatch_sqlite_stmt arm in crates/perry-stdlib/src/common/dispatch.rs catches the dynamic-dispatch path and routes through extern "C" declarations of js_sqlite_stmt_*. It NaN-boxes the returned handle as POINTER via js_nanbox_pointer, so the chained .all() sees a real heap-pointer-shaped receiver and routes back through the same dispatcher.
Why the class-method-internal shape doesn't
The codegen for obj.method() inside a class method body uses a "dispatch tower" path at crates/perry-codegen/src/lower_call.rs:1697-1965 that walks every class in class_ids for an implementor of method. When no class implements the method (e.g., raw() on a native handle), the if-block is skipped and execution falls through. The fall-through path appears to take the static dispatch via the NativeMethodCall machinery, but somewhere in there the result of .raw() (an i64 handle) doesn't get NaN-boxed as POINTER before being passed as the receiver to the chained .all().
Acceptance
tests/release/packages/drizzle-sqlite/entry.ts produces output byte-identical to expected.txt. v0.5.764's regression test test-files/test_better_sqlite3_raw.ts already covers the direct-call shape; this followup needs the class-method-internal shape covered too.
Refs
- drizzle: SQLiteInsertBase methods (_prepare, prepare, execute, returning, etc) return undefined when accessed via Any-typed receiver #643 (closed-partial:
_prepare/prepare/executemethods exist;.raw()infrastructure added) - Drizzle acceptance: 50-line program against real Postgres via @perryts/postgres #488 (drizzle Postgres acceptance — same dispatch tower issue likely affects the
await ibvchain after getSQL returns) - v0.5.764 commit message has the full investigation summary
Activity
fix: refs #645 deeper followup — three coordinated bugs blocking driz…
fix(codegen): closes #645 deeper followup — cross-module IIFE static …
fix(hir): refs #645 deeper / #488 drizzle-sqlite — default-arg fill m…
loop sweep >600: closes #656 + #653 + refs #650 + refs #645 followup …
loop sweep >600: closes #656 + #653 + refs #650 + refs #645 followup …