Skip to content

Commit e5eef99

Browse files
authored
Merge pull request WiseLibs#61 from JoshuaWise/iterate-method
Replace the each() method with an iterate() method
2 parents 20836a7 + d2f452f commit e5eef99

17 files changed

+326
-131
lines changed

.travis.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
language: node_js
22
sudo: false
33
node_js:
4-
- "4"
5-
- "5"
64
- "6"
75
- "7"
86
- "8"
@@ -14,4 +12,4 @@ addons:
1412
- gcc-4.9
1513
- g++-4.9
1614
before_install:
17-
- export CC="gcc-4.9" CXX="g++-4.9"
15+
- export CC="gcc-4.9" CXX="g++-4.9"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ The fastest and simplest library for SQLite3 in Node.js.
1010

1111
## How other libraries compare
1212

13-
| |select 1 row `get()`|select 100 rows `all()`|select 100 rows `each()`|insert 1 row `run()`|insert 100 rows in a transaction|
13+
| |select 1 row `get()`|select 100 rows `all()`|select 100 rows `iterate()`|insert 1 row `run()`|insert 100 rows in a transaction|
1414
|---|---|---|---|---|---|
1515
|better-sqlite3|1x|1x|1x|1x|1x|
16-
|[sqlite](https://www.npmjs.com/package/sqlite) and [sqlite3](https://www.npmjs.com/package/sqlite3)|7.8x slower|3.4x slower|3.4x slower|3.5x slower|6.2x slower|
16+
|[sqlite](https://www.npmjs.com/package/sqlite) and [sqlite3](https://www.npmjs.com/package/sqlite3)|8.4x slower|3.7x slower|28.2x slower|3.6x slower|6.0x slower|
1717

1818
> You can verify these results by [running the benchmark yourself](https://github.com/JoshuaWise/better-sqlite3/wiki/Benchmark).
1919
> *Both [sqlite](https://www.npmjs.com/package/sqlite) and [sqlite3](https://www.npmjs.com/package/sqlite3) have nearly identical performance because they both use the [same engine](https://github.com/mapbox/node-sqlite3).*

benchmark/trials.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports.default = [
44
{type: 'select', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul']},
55
{type: 'select-all', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul']},
6-
{type: 'select-each', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul']},
6+
{type: 'select-iterate', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul']},
77
{type: 'insert', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'], pragma: ['journal_mode = WAL', 'synchronous = 1']},
88
{type: 'insert', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'], pragma: ['journal_mode = DELETE', 'synchronous = 2']},
99
{type: 'transaction', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul']},
@@ -26,13 +26,13 @@ exports.searchable = [
2626
{type: 'select-all', table: 'allSmall', columns: ['nul']},
2727
{type: 'select-all', table: 'allLarge', columns: ['text']},
2828
{type: 'select-all', table: 'allLarge', columns: ['blob']},
29-
{type: 'select-each', table: 'allSmall', columns: ['integer']},
30-
{type: 'select-each', table: 'allSmall', columns: ['real']},
31-
{type: 'select-each', table: 'allSmall', columns: ['text']},
32-
{type: 'select-each', table: 'allSmall', columns: ['blob']},
33-
{type: 'select-each', table: 'allSmall', columns: ['nul']},
34-
{type: 'select-each', table: 'allLarge', columns: ['text']},
35-
{type: 'select-each', table: 'allLarge', columns: ['blob']},
29+
{type: 'select-iterate', table: 'allSmall', columns: ['integer']},
30+
{type: 'select-iterate', table: 'allSmall', columns: ['real']},
31+
{type: 'select-iterate', table: 'allSmall', columns: ['text']},
32+
{type: 'select-iterate', table: 'allSmall', columns: ['blob']},
33+
{type: 'select-iterate', table: 'allSmall', columns: ['nul']},
34+
{type: 'select-iterate', table: 'allLarge', columns: ['text']},
35+
{type: 'select-iterate', table: 'allLarge', columns: ['blob']},
3636
{type: 'insert', table: 'integerSmall', columns: ['integer'], pragma: ['journal_mode = WAL', 'synchronous = 1']},
3737
{type: 'insert', table: 'realSmall', columns: ['real'], pragma: ['journal_mode = WAL', 'synchronous = 1']},
3838
{type: 'insert', table: 'textSmall', columns: ['text'], pragma: ['journal_mode = WAL', 'synchronous = 1']},

benchmark/types/select-all.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ require('../runner')(function (benchmark, dbs, ctx) {
55
var betterSqlite3 = dbs['better-sqlite3'];
66
var nodeSqlite3 = dbs['node-sqlite3'];
77
var rowid = 99;
8-
benchmark.on('cycle', function () {rowid = 0;});
8+
benchmark.on('cycle', function () {rowid = 99;});
99

1010
var betterSqlite3Select = betterSqlite3.prepare(SQL);
1111

benchmark/types/select-each.js renamed to benchmark/types/select-iterate.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,27 @@
22
// Selects 100 rows
33
require('../runner')(function (benchmark, dbs, ctx) {
44
var SQL = 'SELECT ' + ctx.columns.join(', ') + ' FROM ' + ctx.table + ' WHERE rowid>=? LIMIT 100';
5+
var oneByOneSQL = SQL.replace(/\bLIMIT\s+\d+/i, 'LIMIT 1');
56
var betterSqlite3 = dbs['better-sqlite3'];
67
var nodeSqlite3 = dbs['node-sqlite3'];
78
var rowid = 99;
8-
benchmark.on('cycle', function () {rowid = 0;});
9+
benchmark.on('cycle', function () {rowid = 99;});
910

1011
var betterSqlite3Select = betterSqlite3.prepare(SQL);
1112

1213
benchmark.add('better-sqlite3', function () {
13-
betterSqlite3Select.each(rowid % 1000 - 98, function () {});
14+
for (var obj of betterSqlite3Select.iterate(rowid % 1000 - 98)) {}
1415
rowid += 100;
1516
});
1617
benchmark.add('node-sqlite3', function (deferred) {
17-
nodeSqlite3.each(SQL, rowid % 1000 - 98, function () {}).then(function () {deferred.resolve();});
18-
rowid += 100;
18+
var goal = rowid + 100;
19+
function next() {
20+
if (++rowid === goal) {
21+
deferred.resolve();
22+
} else {
23+
nodeSqlite3.get(oneByOneSQL, rowid % 1000 - 98).then(next);
24+
}
25+
}
26+
nodeSqlite3.get(oneByOneSQL, rowid % 1000 - 98).then(next);
1927
});
2028
});

src/better_sqlite3.lzz

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ NODE_MODULE(better_sqlite3, RegisterModule);
2626
#insert "objects/database.lzz"
2727
#insert "objects/statement.lzz"
2828
#insert "objects/transaction.lzz"
29+
#insert "objects/statement-iterator.lzz"
2930
#insert "util/custom-function.lzz"
3031
#insert "util/custom-aggregate.lzz"
3132
#insert "util/data.lzz"
@@ -40,4 +41,5 @@ void RegisterModule(v8::Local<v8::Object> exports, v8::Local<v8::Object> module)
4041
Database::Init(isolate, exports, module);
4142
Statement::Init(isolate, exports, module);
4243
Transaction::Init(isolate, exports, module);
44+
StatementIterator::Init(isolate, exports, module);
4345
}

src/objects/statement-iterator.lzz

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
class StatementIterator : public node::ObjectWrap {
2+
public:
3+
4+
// Provides public access to the constructor.
5+
static v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate, NODE_ARGUMENTS info) {
6+
v8::Local<v8::Function> c = v8::Local<v8::Function>::New(isolate, constructor);
7+
caller_info = &info;
8+
v8::MaybeLocal<v8::Object> maybe_iter = c->NewInstance(OnlyContext, 0, NULL);
9+
caller_info = NULL;
10+
return maybe_iter;
11+
}
12+
13+
// The ~Statement destructor currently covers any state this object creates.
14+
// Additionally, we actually DON'T want to set db_state->busy in this
15+
// destructor, to ensure deterministic database access.
16+
~StatementIterator() {}
17+
18+
private:
19+
20+
explicit StatementIterator(Statement* _stmt, bool _bound) : node::ObjectWrap(),
21+
stmt(_stmt),
22+
handle(_stmt->handle),
23+
safe_ints(_stmt->safe_ints),
24+
pluck(_stmt->pluck),
25+
bound(_bound),
26+
alive(true) {
27+
assert(stmt != NULL);
28+
assert(handle != NULL);
29+
assert(stmt->bound == bound);
30+
assert(stmt->alive == true);
31+
}
32+
33+
REGISTER(Init) {
34+
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate, JS_new);
35+
t->InstanceTemplate()->SetInternalFieldCount(1);
36+
t->SetClassName(StringFromUtf8(isolate, "StatementIterator", -1));
37+
38+
NODE_SET_PROTOTYPE_METHOD(t, "next", JS_next);
39+
NODE_SET_PROTOTYPE_METHOD(t, "return", JS_return);
40+
NODE_SET_PROTOTYPE_SYMBOL_METHOD(t, v8::Symbol::GetIterator(isolate), JS_symbolIterator);
41+
42+
constructor.Reset(isolate, t->GetFunction(OnlyContext).ToLocalChecked());
43+
caller_info = NULL;
44+
}
45+
46+
NODE_METHOD(JS_new) {
47+
if (caller_info == NULL) return ThrowTypeError("Disabled constructor");
48+
assert(info.IsConstructCall());
49+
50+
StatementIterator* iter;
51+
{
52+
NODE_ARGUMENTS info = *caller_info;
53+
STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA);
54+
iter = new StatementIterator(stmt, bound);
55+
db_state->busy = true;
56+
}
57+
UseIsolateAndContext;
58+
iter->Wrap(info.This());
59+
SetFrozen(isolate, ctx, info.This(), CS::statement, caller_info->This());
60+
61+
info.GetReturnValue().Set(info.This());
62+
}
63+
64+
NODE_METHOD(JS_next) {
65+
StatementIterator* iter = Unwrap<StatementIterator>(info.This());
66+
if (iter->alive) iter->Next(info);
67+
else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
68+
}
69+
70+
NODE_METHOD(JS_return) {
71+
StatementIterator* iter = Unwrap<StatementIterator>(info.This());
72+
if (iter->alive) iter->Return(info);
73+
else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
74+
}
75+
76+
NODE_METHOD(JS_symbolIterator) {
77+
info.GetReturnValue().Set(info.This());
78+
}
79+
80+
void Next(NODE_ARGUMENTS info) {
81+
assert(alive == true);
82+
int status = sqlite3_step(handle);
83+
if (status == SQLITE_ROW) {
84+
UseIsolateAndContext;
85+
info.GetReturnValue().Set(
86+
NewRecord(isolate, ctx, pluck
87+
? Data::GetValueJS(isolate, handle, 0, safe_ints)
88+
: Data::GetRowJS(isolate, ctx, handle, safe_ints))
89+
);
90+
} else {
91+
if (status == SQLITE_DONE) Return(info);
92+
else Throw();
93+
}
94+
}
95+
96+
void Return(NODE_ARGUMENTS info) {
97+
Cleanup();
98+
STATEMENT_RETURN(DoneRecord(OnlyIsolate));
99+
}
100+
101+
void Throw() {
102+
Cleanup();
103+
Database* db = stmt->db;
104+
STATEMENT_THROW();
105+
}
106+
107+
void Cleanup() {
108+
assert(alive == true);
109+
alive = false;
110+
sqlite3_reset(handle);
111+
stmt->db->GetState()->busy = false;
112+
}
113+
114+
static v8::Local<v8::Object> NewRecord(v8::Isolate* isolate, v8::Local<v8::Context> ctx, v8::Local<v8::Value> value, bool done = false) {
115+
v8::Local<v8::Object> record = v8::Object::New(isolate);
116+
record->Set(ctx, CS::Get(isolate, CS::value), value).FromJust();
117+
record->Set(ctx, CS::Get(isolate, CS::done), done ? v8::True(isolate) : v8::False(isolate)).FromJust();
118+
return record;
119+
}
120+
121+
static v8::Local<v8::Object> DoneRecord(v8::Isolate* isolate) {
122+
return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), true);
123+
}
124+
125+
static v8::Persistent<v8::Function> constructor;
126+
static const NODE_ARGUMENTS_TYPE* caller_info;
127+
128+
Statement* const stmt;
129+
sqlite3_stmt* const handle;
130+
const bool safe_ints;
131+
const bool pluck;
132+
const bool bound;
133+
bool alive;
134+
};

src/objects/statement.lzz

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
class Statement : public node::ObjectWrap, public Query {
2+
friend class StatementIterator;
23
public:
34

45
// Provides public access to the constructor.
@@ -28,7 +29,6 @@ public:
2829
void CloseHandles() {
2930
if (alive) {
3031
alive = false;
31-
if (bound) sqlite3_clear_bindings(handle);
3232
sqlite3_finalize(handle);
3333
}
3434
}
@@ -64,7 +64,7 @@ private:
6464
NODE_SET_PROTOTYPE_METHOD(t, "run", JS_run);
6565
NODE_SET_PROTOTYPE_METHOD(t, "get", JS_get);
6666
NODE_SET_PROTOTYPE_METHOD(t, "all", JS_all);
67-
NODE_SET_PROTOTYPE_METHOD(t, "each", JS_each);
67+
NODE_SET_PROTOTYPE_METHOD(t, "iterate", JS_iterate);
6868
NODE_SET_PROTOTYPE_METHOD(t, "bind", JS_bind);
6969
NODE_SET_PROTOTYPE_METHOD(t, "pluck", JS_pluck);
7070
NODE_SET_PROTOTYPE_METHOD(t, "safeIntegers", JS_safeIntegers);
@@ -112,7 +112,7 @@ private:
112112
}
113113

114114
NODE_METHOD(JS_run) {
115-
STATEMENT_START(info.Length(), REQUIRE_STATEMENT_DOESNT_RETURN_DATA);
115+
STATEMENT_START(REQUIRE_STATEMENT_DOESNT_RETURN_DATA);
116116
sqlite3* db_handle = db->GetHandle();
117117
int total_changes_before = sqlite3_total_changes(db_handle);
118118

@@ -130,7 +130,7 @@ private:
130130
}
131131

132132
NODE_METHOD(JS_get) {
133-
STATEMENT_START(info.Length(), REQUIRE_STATEMENT_RETURNS_DATA);
133+
STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA);
134134
UseIsolate;
135135
int status = sqlite3_step(handle);
136136
if (status == SQLITE_ROW) {
@@ -148,7 +148,7 @@ private:
148148
}
149149

150150
NODE_METHOD(JS_all) {
151-
STATEMENT_START(info.Length(), REQUIRE_STATEMENT_RETURNS_DATA);
151+
STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA);
152152
UseIsolateAndContext;
153153
v8::Local<v8::Array> result = v8::Array::New(isolate, 0);
154154
uint32_t row_count = 0;
@@ -171,39 +171,17 @@ private:
171171
STATEMENT_THROW();
172172
}
173173

174-
NODE_METHOD(JS_each) {
175-
REQUIRE_LAST_ARGUMENT_FUNCTION(bind_args_count, callback);
176-
STATEMENT_START(bind_args_count, REQUIRE_STATEMENT_RETURNS_DATA);
177-
UseIsolateAndContext;
178-
v8::Local<v8::Primitive> Undefined = v8::Undefined(isolate);
179-
const bool safe_ints = stmt->safe_ints;
180-
const bool pluck = stmt->pluck;
181-
bool js_error = false;
182-
183-
db_state->busy = true;
184-
while (sqlite3_step(handle) == SQLITE_ROW) {
185-
NewHandleScope;
186-
v8::Local<v8::Value> arg = pluck
187-
? Data::GetValueJS(isolate, handle, 0, safe_ints)
188-
: Data::GetRowJS(isolate, ctx, handle, safe_ints);
189-
v8::MaybeLocal<v8::Value> callback_return_value = callback->Call(ctx, Undefined, 1, &arg);
190-
if (callback_return_value.IsEmpty()) {js_error = true; break;}
191-
}
192-
db_state->busy = false;
193-
194-
if (sqlite3_reset(handle) == SQLITE_OK && !js_error) {
195-
STATEMENT_RETURN(Undefined);
196-
}
197-
if (js_error) db_state->was_js_error = true;
198-
STATEMENT_THROW();
174+
NODE_METHOD(JS_iterate) {
175+
v8::MaybeLocal<v8::Object> maybe_iter = StatementIterator::New(OnlyIsolate, info);
176+
if (!maybe_iter.IsEmpty()) info.GetReturnValue().Set(maybe_iter.ToLocalChecked());
199177
}
200178

201179
NODE_METHOD(JS_bind) {
202180
Statement* stmt = Unwrap<Statement>(info.This());
203181
if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object");
204182
REQUIRE_DATABASE_OPEN(stmt->db->GetState());
205183
REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
206-
STATEMENT_BIND(stmt->handle, info.Length());
184+
STATEMENT_BIND(stmt->handle);
207185
stmt->bound = true;
208186
info.GetReturnValue().Set(info.This());
209187
}

src/objects/transaction.lzz

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ public:
3131
void CloseHandles() {
3232
if (alive) {
3333
alive = false;
34-
for (int i=0; i<handle_count; ++i) {
35-
if (bound) sqlite3_clear_bindings(handles[i]);
36-
sqlite3_finalize(handles[i]);
37-
}
34+
for (int i=0; i<handle_count; ++i) sqlite3_finalize(handles[i]);
3835
delete[] handles;
3936
}
4037
}

src/util/constants.lzz

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public:
2121
static ConstantString changes;
2222
static ConstantString lastInsertROWID;
2323
static ConstantString code;
24+
static ConstantString statement;
2425

2526
private:
2627
REGISTER(Init) {
@@ -36,6 +37,7 @@ private:
3637
AddString(isolate, CS::changes, "changes");
3738
AddString(isolate, CS::lastInsertROWID, "lastInsertROWID");
3839
AddString(isolate, CS::code, "code");
40+
AddString(isolate, CS::statement, "statement");
3941

4042
AddCode(isolate, SQLITE_OK, "SQLITE_OK");
4143
AddCode(isolate, SQLITE_ERROR, "SQLITE_ERROR");
@@ -135,7 +137,7 @@ private:
135137
static void AddCode(v8::Isolate* isolate, int code, const char* str) {
136138
codes.emplace(std::piecewise_construct, std::forward_as_tuple(code), std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str)));
137139
}
138-
explicit CS(char _) {assert(false);};
140+
explicit CS(char _) {assert(false);}
139141

140142
static std::unordered_map<int, ConstantString> codes;
141143
}

0 commit comments

Comments
 (0)