implemented LocalVector optimizations for newer version of v8

This commit is contained in:
Joshua Wise
2025-07-21 04:21:22 -05:00
parent a162a668a8
commit 08b786b072
10 changed files with 230 additions and 67 deletions
+14 -8
View File
@@ -18,13 +18,19 @@ module.exports = new Map([
for (const str of pragma) await db.run(`PRAGMA ${str}`);
return db;
}],
...!moduleExists('node:sqlite') ? [] : [
['node:sqlite', async (filename, pragma) => {
const db = new (require('node:sqlite').DatabaseSync)(filename);
for (const str of pragma) db.exec(`PRAGMA ${str}`);
return db;
}]
],
]);
const moduleExists = (m) => { try { return require.resolve(m); } catch (e) {} };
if (moduleExists('node:sqlite')) {
module.exports.set('node:sqlite', async (filename, pragma) => {
const db = new (require('node:sqlite').DatabaseSync)(filename, {open: true});
for (const str of pragma) db.exec(`PRAGMA ${str}`);
return db;
});
}
function moduleExists(moduleName) {
try {
return !!(require.resolve(moduleName));
} catch (_) {
return false;
}
};
+6 -11
View File
@@ -3,25 +3,20 @@ exports.readonly = false; // Inserting rows individually (`.run()`)
exports['better-sqlite3'] = (db, { table, columns }) => {
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
const row = db.prepare(`SELECT * FROM ${table} LIMIT 1`).get();
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
return () => stmt.run(row);
};
exports['node-sqlite3'] = async (db, { table, columns }) => {
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT * FROM ${table} LIMIT 1`))
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`))
.filter(([k]) => columns.includes(k))
.map(([k, v]) => ({ ['@' + k]: v })));
return () => db.run(sql, row);
};
exports['node:sqlite'] = (db, { table, columns }) => {
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
const row = Object.assign({}, ...Object.entries(db.prepare(`SELECT * FROM ${table} LIMIT 1`).get())
.filter(([k]) => columns.includes(k))
.map(([k, v]) => ({ ['@' + k]: v })));
return () => {
const stmt = db.prepare(sql);
return stmt.run(row);
}
};
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
return () => stmt.run(row);
};
+3 -6
View File
@@ -14,10 +14,7 @@ exports['node-sqlite3'] = async (db, { table, columns, count }) => {
};
exports['node:sqlite'] = (db, { table, columns, count }) => {
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`;
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`);
let rowid = -100;
return () => {
const stmt = db.prepare(sql);
return stmt.all((rowid += 100) % count + 1);
}
};
return () => stmt.all((rowid += 100) % count + 1);
};
+3 -11
View File
@@ -23,17 +23,9 @@ exports['node-sqlite3'] = async (db, { table, columns, count }) => {
};
exports['node:sqlite'] = (db, { table, columns, count }) => {
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`;
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`);
let rowid = -100;
if (!("iterate" in require("node:sqlite").StatementSync.prototype)) {
// Error: StatementSync.iterate is not a function (added in Node v23.4.0+)
return () => {};
}
return () => {
// Error: statement has been finalized
// for (const row of db.prepare(sql).iterate((rowid += 100) % count + 1)) {}
return () => {};
for (const row of stmt.iterate((rowid += 100) % count + 1)) {}
};
};
};
+3 -6
View File
@@ -14,10 +14,7 @@ exports['node-sqlite3'] = async (db, { table, columns, count }) => {
};
exports['node:sqlite'] = (db, { table, columns, count }) => {
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`;
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`);
let rowid = -1;
return () => {
const stmt = db.prepare(sql);
return stmt.get(++rowid % count + 1);
}
};
return () => stmt.get(++rowid % count + 1);
};
+13 -11
View File
@@ -3,7 +3,7 @@ exports.readonly = false; // Inserting 100 rows in a single transaction
exports['better-sqlite3'] = (db, { table, columns }) => {
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
const row = db.prepare(`SELECT * FROM ${table} LIMIT 1`).get();
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
const trx = db.transaction((row) => {
for (let i = 0; i < 100; ++i) stmt.run(row);
});
@@ -12,7 +12,7 @@ exports['better-sqlite3'] = (db, { table, columns }) => {
exports['node-sqlite3'] = async (db, { table, columns, driver, pragma }) => {
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT * FROM ${table} LIMIT 1`))
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`))
.filter(([k]) => columns.includes(k))
.map(([k, v]) => ({ ['@' + k]: v })));
const open = require('../drivers').get(driver);
@@ -40,14 +40,16 @@ exports['node-sqlite3'] = async (db, { table, columns, driver, pragma }) => {
};
exports['node:sqlite'] = (db, { table, columns }) => {
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
const row = Object.assign({}, ...Object.entries(db.prepare(`SELECT * FROM ${table} LIMIT 1`).get())
.filter(([k]) => columns.includes(k))
.map(([k, v]) => ({ ['@' + k]: v })));
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
return () => {
const stmt = db.prepare(sql);
db.exec(`BEGIN`);
for (let i = 0; i < 100; ++i) stmt.run(row);
db.exec(`COMMIT`);
db.exec('BEGIN');
try {
for (let i = 0; i < 100; ++i) stmt.run(row);
db.exec('COMMIT');
} catch (err) {
db.isTransaction && db.exec('ROLLBACK');
throw err;
}
}
};
};
+3
View File
@@ -23,6 +23,9 @@ class Backup;
#include "util/bind-map.cpp"
#include "util/data-converter.cpp"
#include "util/data.cpp"
#if defined(NODE_MODULE_VERSION) && NODE_MODULE_VERSION >= 127
#include "util/row-builder.cpp"
#endif
#include "objects/backup.hpp"
#include "objects/statement.hpp"
+76 -3
View File
@@ -180,11 +180,13 @@ NODE_METHOD(Statement::JS_get) {
NODE_METHOD(Statement::JS_all) {
STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE);
UseContext;
v8::Local<v8::Array> result = v8::Array::New(isolate, 0);
uint32_t row_count = 0;
const bool safe_ints = stmt->safe_ints;
const char mode = stmt->mode;
#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
bool js_error = false;
uint32_t row_count = 0;
v8::Local<v8::Array> result = v8::Array::New(isolate, 0);
while (sqlite3_step(handle) == SQLITE_ROW) {
if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; }
@@ -196,6 +198,31 @@ NODE_METHOD(Statement::JS_all) {
}
if (js_error) db->GetState()->was_js_error = true;
STATEMENT_THROW();
#else
v8::LocalVector<v8::Value> rows(isolate);
rows.reserve(8);
if (mode == Data::FLAT) {
RowBuilder rowBuilder(isolate, handle, safe_ints);
while (sqlite3_step(handle) == SQLITE_ROW) {
rows.emplace_back(rowBuilder.GetRowJS());
}
} else {
while (sqlite3_step(handle) == SQLITE_ROW) {
rows.emplace_back(Data::GetRowJS(isolate, ctx, handle, safe_ints, mode));
}
}
if (sqlite3_reset(handle) == SQLITE_OK) {
if (rows.size() > 0xffffffff) {
ThrowRangeError("Array overflow (too many rows returned)");
db->GetState()->was_js_error = true;
} else {
STATEMENT_RETURN(v8::Array::New(isolate, rows.data(), rows.size()));
}
}
STATEMENT_THROW();
#endif
}
NODE_METHOD(Statement::JS_iterate) {
@@ -268,8 +295,9 @@ NODE_METHOD(Statement::JS_columns) {
REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
Addon* addon = stmt->db->GetAddon();
UseIsolate;
UseContext;
#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
UseContext;
int column_count = sqlite3_column_count(stmt->handle);
v8::Local<v8::Array> columns = v8::Array::New(isolate);
@@ -302,6 +330,51 @@ NODE_METHOD(Statement::JS_columns) {
}
info.GetReturnValue().Set(columns);
#else
v8::LocalVector<v8::Name> keys(isolate);
keys.reserve(5);
keys.emplace_back(addon->cs.name.Get(isolate).As<v8::Name>());
keys.emplace_back(addon->cs.column.Get(isolate).As<v8::Name>());
keys.emplace_back(addon->cs.table.Get(isolate).As<v8::Name>());
keys.emplace_back(addon->cs.database.Get(isolate).As<v8::Name>());
keys.emplace_back(addon->cs.type.Get(isolate).As<v8::Name>());
int column_count = sqlite3_column_count(stmt->handle);
v8::LocalVector<v8::Value> columns(isolate);
columns.reserve(column_count);
for (int i = 0; i < column_count; ++i) {
v8::LocalVector<v8::Value> values(isolate);
keys.reserve(5);
values.emplace_back(
InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1)
);
values.emplace_back(
InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1)
);
values.emplace_back(
InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1)
);
values.emplace_back(
InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1)
);
values.emplace_back(
InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1)
);
columns.emplace_back(
v8::Object::New(isolate,
v8::Null(isolate),
keys.data(),
values.data(),
keys.size()
)
);
}
info.GetReturnValue().Set(
v8::Array::New(isolate, columns.data(), columns.size())
);
#endif
}
NODE_GETTER(Statement::JS_busy) {
+60 -11
View File
@@ -77,17 +77,6 @@ namespace Data {
SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value);
}
v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
v8::Local<v8::Object> row = v8::Object::New(isolate);
int column_count = sqlite3_column_count(handle);
for (int i = 0; i < column_count; ++i) {
row->Set(ctx,
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1),
Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust();
}
return row;
}
v8::Local<v8::Value> GetExpandedRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
v8::Local<v8::Object> row = v8::Object::New(isolate);
int column_count = sqlite3_column_count(handle);
@@ -107,6 +96,20 @@ namespace Data {
return row;
}
#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
v8::Local<v8::Object> row = v8::Object::New(isolate);
int column_count = sqlite3_column_count(handle);
for (int i = 0; i < column_count; ++i) {
row->Set(ctx,
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1),
Data::GetValueJS(isolate, handle, i, safe_ints)
).FromJust();
}
return row;
}
v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
v8::Local<v8::Array> row = v8::Array::New(isolate);
int column_count = sqlite3_column_count(handle);
@@ -125,6 +128,52 @@ namespace Data {
return v8::Local<v8::Value>();
}
#else
v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) {
int column_count = sqlite3_column_count(handle);
v8::LocalVector<v8::Name> keys(isolate);
v8::LocalVector<v8::Value> values(isolate);
keys.reserve(column_count);
values.reserve(column_count);
for (int i = 0; i < column_count; ++i) {
keys.emplace_back(
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1).As<v8::Name>()
);
values.emplace_back(
Data::GetValueJS(isolate, handle, i, safe_ints)
);
}
return v8::Object::New(
isolate,
v8::Null(isolate),
keys.data(),
values.data(),
column_count
);
}
v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) {
int column_count = sqlite3_column_count(handle);
v8::LocalVector<v8::Value> row(isolate);
row.reserve(column_count);
for (int i = 0; i < column_count; ++i) {
row.emplace_back(Data::GetValueJS(isolate, handle, i, safe_ints));
}
return v8::Array::New(isolate, row.data(), row.size());
}
v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints, char mode) {
if (mode == FLAT) return GetFlatRowJS(isolate, handle, safe_ints);
if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints);
if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints);
if (mode == RAW) return GetRawRowJS(isolate, handle, safe_ints);
assert(false);
return v8::Local<v8::Value>();
}
#endif
void GetArgumentsJS(v8::Isolate* isolate, v8::Local<v8::Value>* out, sqlite3_value** values, int argument_count, bool safe_ints) {
assert(argument_count > 0);
for (int i = 0; i < argument_count; ++i) {
+49
View File
@@ -0,0 +1,49 @@
class RowBuilder {
public:
explicit RowBuilder(
v8::Isolate* isolate,
sqlite3_stmt* handle,
bool safe_ints
) :
isolate(isolate),
handle(handle),
column_count(-1),
safe_ints(safe_ints),
keys(isolate) {}
v8::Local<v8::Value> GetRowJS() {
if (column_count < 0) {
column_count = sqlite3_column_count(handle);
keys.reserve(column_count);
for (int i = 0; i < column_count; ++i) {
keys.emplace_back(
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1)
.As<v8::Name>()
);
}
}
v8::LocalVector<v8::Value> values(isolate);
values.reserve(column_count);
for (int i = 0; i < column_count; ++i) {
values.emplace_back(
Data::GetValueJS(isolate, handle, i, safe_ints)
);
}
return v8::Object::New(isolate,
v8::Null(isolate),
keys.data(),
values.data(),
column_count
);
}
private:
v8::Isolate* isolate;
sqlite3_stmt* handle;
int column_count;
const bool safe_ints;
v8::LocalVector<v8::Name> keys;
};