Skip to content

Commit ed7c7b9

Browse files
committed
implemented LocalVector optimizations for newer version of v8
1 parent b35748f commit ed7c7b9

File tree

10 files changed

+230
-67
lines changed

10 files changed

+230
-67
lines changed

benchmark/drivers.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@ module.exports = new Map([
1818
for (const str of pragma) await db.run(`PRAGMA ${str}`);
1919
return db;
2020
}],
21+
...!moduleExists('node:sqlite') ? [] : [
22+
['node:sqlite', async (filename, pragma) => {
23+
const db = new (require('node:sqlite').DatabaseSync)(filename);
24+
for (const str of pragma) db.exec(`PRAGMA ${str}`);
25+
return db;
26+
}]
27+
],
2128
]);
2229

23-
const moduleExists = (m) => { try { return require.resolve(m); } catch (e) {} };
24-
if (moduleExists('node:sqlite')) {
25-
module.exports.set('node:sqlite', async (filename, pragma) => {
26-
const db = new (require('node:sqlite').DatabaseSync)(filename, {open: true});
27-
for (const str of pragma) db.exec(`PRAGMA ${str}`);
28-
return db;
29-
});
30-
}
30+
function moduleExists(moduleName) {
31+
try {
32+
return !!(require.resolve(moduleName));
33+
} catch (_) {
34+
return false;
35+
}
36+
};

benchmark/types/insert.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,20 @@ exports.readonly = false; // Inserting rows individually (`.run()`)
33

44
exports['better-sqlite3'] = (db, { table, columns }) => {
55
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
6-
const row = db.prepare(`SELECT * FROM ${table} LIMIT 1`).get();
6+
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
77
return () => stmt.run(row);
88
};
99

1010
exports['node-sqlite3'] = async (db, { table, columns }) => {
1111
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
12-
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT * FROM ${table} LIMIT 1`))
12+
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`))
1313
.filter(([k]) => columns.includes(k))
1414
.map(([k, v]) => ({ ['@' + k]: v })));
1515
return () => db.run(sql, row);
1616
};
1717

1818
exports['node:sqlite'] = (db, { table, columns }) => {
19-
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
20-
const row = Object.assign({}, ...Object.entries(db.prepare(`SELECT * FROM ${table} LIMIT 1`).get())
21-
.filter(([k]) => columns.includes(k))
22-
.map(([k, v]) => ({ ['@' + k]: v })));
23-
return () => {
24-
const stmt = db.prepare(sql);
25-
return stmt.run(row);
26-
}
27-
};
19+
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
20+
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
21+
return () => stmt.run(row);
22+
};

benchmark/types/select-all.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ exports['node-sqlite3'] = async (db, { table, columns, count }) => {
1414
};
1515

1616
exports['node:sqlite'] = (db, { table, columns, count }) => {
17-
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`;
17+
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`);
1818
let rowid = -100;
19-
return () => {
20-
const stmt = db.prepare(sql);
21-
return stmt.all((rowid += 100) % count + 1);
22-
}
23-
};
19+
return () => stmt.all((rowid += 100) % count + 1);
20+
};

benchmark/types/select-iterate.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,9 @@ exports['node-sqlite3'] = async (db, { table, columns, count }) => {
2323
};
2424

2525
exports['node:sqlite'] = (db, { table, columns, count }) => {
26-
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`;
26+
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`);
2727
let rowid = -100;
28-
29-
if (!("iterate" in require("node:sqlite").StatementSync.prototype)) {
30-
// Error: StatementSync.iterate is not a function (added in Node v23.4.0+)
31-
return () => {};
32-
}
33-
3428
return () => {
35-
// Error: statement has been finalized
36-
// for (const row of db.prepare(sql).iterate((rowid += 100) % count + 1)) {}
37-
return () => {};
29+
for (const row of stmt.iterate((rowid += 100) % count + 1)) {}
3830
};
39-
};
31+
};

benchmark/types/select.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ exports['node-sqlite3'] = async (db, { table, columns, count }) => {
1414
};
1515

1616
exports['node:sqlite'] = (db, { table, columns, count }) => {
17-
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`;
17+
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`);
1818
let rowid = -1;
19-
return () => {
20-
const stmt = db.prepare(sql);
21-
return stmt.get(++rowid % count + 1);
22-
}
23-
};
19+
return () => stmt.get(++rowid % count + 1);
20+
};

benchmark/types/transaction.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ exports.readonly = false; // Inserting 100 rows in a single transaction
33

44
exports['better-sqlite3'] = (db, { table, columns }) => {
55
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
6-
const row = db.prepare(`SELECT * FROM ${table} LIMIT 1`).get();
6+
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
77
const trx = db.transaction((row) => {
88
for (let i = 0; i < 100; ++i) stmt.run(row);
99
});
@@ -12,7 +12,7 @@ exports['better-sqlite3'] = (db, { table, columns }) => {
1212

1313
exports['node-sqlite3'] = async (db, { table, columns, driver, pragma }) => {
1414
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
15-
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT * FROM ${table} LIMIT 1`))
15+
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`))
1616
.filter(([k]) => columns.includes(k))
1717
.map(([k, v]) => ({ ['@' + k]: v })));
1818
const open = require('../drivers').get(driver);
@@ -40,14 +40,16 @@ exports['node-sqlite3'] = async (db, { table, columns, driver, pragma }) => {
4040
};
4141

4242
exports['node:sqlite'] = (db, { table, columns }) => {
43-
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`;
44-
const row = Object.assign({}, ...Object.entries(db.prepare(`SELECT * FROM ${table} LIMIT 1`).get())
45-
.filter(([k]) => columns.includes(k))
46-
.map(([k, v]) => ({ ['@' + k]: v })));
43+
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`);
44+
const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get();
4745
return () => {
48-
const stmt = db.prepare(sql);
49-
db.exec(`BEGIN`);
50-
for (let i = 0; i < 100; ++i) stmt.run(row);
51-
db.exec(`COMMIT`);
46+
db.exec('BEGIN');
47+
try {
48+
for (let i = 0; i < 100; ++i) stmt.run(row);
49+
db.exec('COMMIT');
50+
} catch (err) {
51+
db.isTransaction && db.exec('ROLLBACK');
52+
throw err;
53+
}
5254
}
53-
};
55+
};

src/better_sqlite3.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class Backup;
2323
#include "util/bind-map.cpp"
2424
#include "util/data-converter.cpp"
2525
#include "util/data.cpp"
26+
#if defined(NODE_MODULE_VERSION) && NODE_MODULE_VERSION >= 127
27+
#include "util/row-builder.cpp"
28+
#endif
2629

2730
#include "objects/backup.hpp"
2831
#include "objects/statement.hpp"

src/objects/statement.cpp

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,13 @@ NODE_METHOD(Statement::JS_get) {
180180
NODE_METHOD(Statement::JS_all) {
181181
STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE);
182182
UseContext;
183-
v8::Local<v8::Array> result = v8::Array::New(isolate, 0);
184-
uint32_t row_count = 0;
185183
const bool safe_ints = stmt->safe_ints;
186184
const char mode = stmt->mode;
185+
186+
#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
187187
bool js_error = false;
188+
uint32_t row_count = 0;
189+
v8::Local<v8::Array> result = v8::Array::New(isolate, 0);
188190

189191
while (sqlite3_step(handle) == SQLITE_ROW) {
190192
if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; }
@@ -196,6 +198,31 @@ NODE_METHOD(Statement::JS_all) {
196198
}
197199
if (js_error) db->GetState()->was_js_error = true;
198200
STATEMENT_THROW();
201+
#else
202+
v8::LocalVector<v8::Value> rows(isolate);
203+
rows.reserve(8);
204+
205+
if (mode == Data::FLAT) {
206+
RowBuilder rowBuilder(isolate, handle, safe_ints);
207+
while (sqlite3_step(handle) == SQLITE_ROW) {
208+
rows.emplace_back(rowBuilder.GetRowJS());
209+
}
210+
} else {
211+
while (sqlite3_step(handle) == SQLITE_ROW) {
212+
rows.emplace_back(Data::GetRowJS(isolate, ctx, handle, safe_ints, mode));
213+
}
214+
}
215+
216+
if (sqlite3_reset(handle) == SQLITE_OK) {
217+
if (rows.size() > 0xffffffff) {
218+
ThrowRangeError("Array overflow (too many rows returned)");
219+
db->GetState()->was_js_error = true;
220+
} else {
221+
STATEMENT_RETURN(v8::Array::New(isolate, rows.data(), rows.size()));
222+
}
223+
}
224+
STATEMENT_THROW();
225+
#endif
199226
}
200227

201228
NODE_METHOD(Statement::JS_iterate) {
@@ -268,8 +295,9 @@ NODE_METHOD(Statement::JS_columns) {
268295
REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
269296
Addon* addon = stmt->db->GetAddon();
270297
UseIsolate;
271-
UseContext;
272298

299+
#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
300+
UseContext;
273301
int column_count = sqlite3_column_count(stmt->handle);
274302
v8::Local<v8::Array> columns = v8::Array::New(isolate);
275303

@@ -302,6 +330,51 @@ NODE_METHOD(Statement::JS_columns) {
302330
}
303331

304332
info.GetReturnValue().Set(columns);
333+
#else
334+
v8::LocalVector<v8::Name> keys(isolate);
335+
keys.reserve(5);
336+
keys.emplace_back(addon->cs.name.Get(isolate).As<v8::Name>());
337+
keys.emplace_back(addon->cs.column.Get(isolate).As<v8::Name>());
338+
keys.emplace_back(addon->cs.table.Get(isolate).As<v8::Name>());
339+
keys.emplace_back(addon->cs.database.Get(isolate).As<v8::Name>());
340+
keys.emplace_back(addon->cs.type.Get(isolate).As<v8::Name>());
341+
342+
int column_count = sqlite3_column_count(stmt->handle);
343+
v8::LocalVector<v8::Value> columns(isolate);
344+
columns.reserve(column_count);
345+
346+
for (int i = 0; i < column_count; ++i) {
347+
v8::LocalVector<v8::Value> values(isolate);
348+
keys.reserve(5);
349+
values.emplace_back(
350+
InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1)
351+
);
352+
values.emplace_back(
353+
InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1)
354+
);
355+
values.emplace_back(
356+
InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1)
357+
);
358+
values.emplace_back(
359+
InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1)
360+
);
361+
values.emplace_back(
362+
InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1)
363+
);
364+
columns.emplace_back(
365+
v8::Object::New(isolate,
366+
v8::Null(isolate),
367+
keys.data(),
368+
values.data(),
369+
keys.size()
370+
)
371+
);
372+
}
373+
374+
info.GetReturnValue().Set(
375+
v8::Array::New(isolate, columns.data(), columns.size())
376+
);
377+
#endif
305378
}
306379

307380
NODE_GETTER(Statement::JS_busy) {

src/util/data.cpp

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,6 @@ namespace Data {
7777
SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value);
7878
}
7979

80-
v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
81-
v8::Local<v8::Object> row = v8::Object::New(isolate);
82-
int column_count = sqlite3_column_count(handle);
83-
for (int i = 0; i < column_count; ++i) {
84-
row->Set(ctx,
85-
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1),
86-
Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust();
87-
}
88-
return row;
89-
}
90-
9180
v8::Local<v8::Value> GetExpandedRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
9281
v8::Local<v8::Object> row = v8::Object::New(isolate);
9382
int column_count = sqlite3_column_count(handle);
@@ -107,6 +96,20 @@ namespace Data {
10796
return row;
10897
}
10998

99+
#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
100+
101+
v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
102+
v8::Local<v8::Object> row = v8::Object::New(isolate);
103+
int column_count = sqlite3_column_count(handle);
104+
for (int i = 0; i < column_count; ++i) {
105+
row->Set(ctx,
106+
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1),
107+
Data::GetValueJS(isolate, handle, i, safe_ints)
108+
).FromJust();
109+
}
110+
return row;
111+
}
112+
110113
v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
111114
v8::Local<v8::Array> row = v8::Array::New(isolate);
112115
int column_count = sqlite3_column_count(handle);
@@ -125,6 +128,52 @@ namespace Data {
125128
return v8::Local<v8::Value>();
126129
}
127130

131+
#else
132+
133+
v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) {
134+
int column_count = sqlite3_column_count(handle);
135+
v8::LocalVector<v8::Name> keys(isolate);
136+
v8::LocalVector<v8::Value> values(isolate);
137+
keys.reserve(column_count);
138+
values.reserve(column_count);
139+
for (int i = 0; i < column_count; ++i) {
140+
keys.emplace_back(
141+
InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1).As<v8::Name>()
142+
);
143+
values.emplace_back(
144+
Data::GetValueJS(isolate, handle, i, safe_ints)
145+
);
146+
}
147+
return v8::Object::New(
148+
isolate,
149+
v8::Null(isolate),
150+
keys.data(),
151+
values.data(),
152+
column_count
153+
);
154+
}
155+
156+
v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) {
157+
int column_count = sqlite3_column_count(handle);
158+
v8::LocalVector<v8::Value> row(isolate);
159+
row.reserve(column_count);
160+
for (int i = 0; i < column_count; ++i) {
161+
row.emplace_back(Data::GetValueJS(isolate, handle, i, safe_ints));
162+
}
163+
return v8::Array::New(isolate, row.data(), row.size());
164+
}
165+
166+
v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints, char mode) {
167+
if (mode == FLAT) return GetFlatRowJS(isolate, handle, safe_ints);
168+
if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints);
169+
if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints);
170+
if (mode == RAW) return GetRawRowJS(isolate, handle, safe_ints);
171+
assert(false);
172+
return v8::Local<v8::Value>();
173+
}
174+
175+
#endif
176+
128177
void GetArgumentsJS(v8::Isolate* isolate, v8::Local<v8::Value>* out, sqlite3_value** values, int argument_count, bool safe_ints) {
129178
assert(argument_count > 0);
130179
for (int i = 0; i < argument_count; ++i) {

0 commit comments

Comments
 (0)