Skip to content

drizzle: this.stmt.raw().all(...) from inside PreparedQuery.values() returns (number).all #645

Closed

Description

@proggeramlug

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

Activity

  1. added 5 commits that reference this issue on May 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions