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.
Current behavior
Rows returned by
get(),all()anditerate()are created with anullprototype. 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.run(), which already returns an ordinary object, and withbetter-sqlite3.Cons
Object.hasOwn()—row.toStringandrow.constructorstart resolving throughObject.prototype.{ __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 reachesObject.prototype.I have benchmarks and a working implementation if there is interest.