Skip to content

sqlite: remove the null prototype from result rows #65799

Description

@araujogui

Current behavior

Rows returned by get(), all() and iterate() are created with a null prototype. The V8 API that does this produces dictionary-mode objects, and V8 will not cache a prototype transition on a dictionary map, so every row also gets its own freshly allocated hidden class. Rows from the same statement therefore share no shape: they are slow to build, and every property access on them in user code is megamorphic.

Proposal

Drop the null prototype and build rows with v8::DictionaryTemplate, cached per statement. Rows become ordinary objects, and every row of a statement shares one hidden class.

Pros

  • all() is 10–37% faster depending on the query; reading the rows afterwards is far cheaper still.
  • Consistent with run(), which already returns an ordinary object, and with better-sqlite3.

Cons

  • Semver-major.
  • Rows can no longer be indexed by untrusted keys without Object.hasOwn()row.toString and row.constructor start resolving through Object.prototype.
  • User code comparing rows against { __proto__: null, ... } breaks.

Prototype pollution is not a concern either way: rows are built by defining own properties directly, so a __proto__ column is an own property and never reaches Object.prototype.

I have benchmarks and a working implementation if there is interest.

Activity

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