Skip to content

Commit dbf13de

Browse files
committed
sqlite: add fast api to DatabaseSync.isTransaction
1 parent 4cd8e19 commit dbf13de

File tree

4 files changed

+262
-9
lines changed

4 files changed

+262
-9
lines changed

src/node_sqlite.cc

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
#include "env-inl.h"
66
#include "memory_tracker-inl.h"
77
#include "node.h"
8+
#include "node_debug.h"
89
#include "node_errors.h"
910
#include "node_mem-inl.h"
1011
#include "node_url.h"
1112
#include "sqlite3.h"
1213
#include "threadpoolwork-inl.h"
1314
#include "util-inl.h"
15+
#include "v8-fast-api-calls.h"
1416

1517
#include <cinttypes>
1618

@@ -21,10 +23,12 @@ using v8::Array;
2123
using v8::ArrayBuffer;
2224
using v8::BigInt;
2325
using v8::Boolean;
26+
using v8::CFunction;
2427
using v8::ConstructorBehavior;
2528
using v8::Context;
2629
using v8::DontDelete;
2730
using v8::Exception;
31+
using v8::FastApiCallbackOptions;
2832
using v8::Function;
2933
using v8::FunctionCallback;
3034
using v8::FunctionCallbackInfo;
@@ -58,11 +62,11 @@ using v8::Value;
5862
} \
5963
} while (0)
6064

61-
#define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \
65+
#define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg, ...) \
6266
do { \
6367
if ((condition)) { \
6468
THROW_ERR_INVALID_STATE((env), (msg)); \
65-
return; \
69+
return __VA_ARGS__; \
6670
} \
6771
} while (0)
6872

@@ -979,7 +983,7 @@ void DatabaseSync::IsOpenGetter(const FunctionCallbackInfo<Value>& args) {
979983
args.GetReturnValue().Set(db->IsOpen());
980984
}
981985

982-
void DatabaseSync::IsTransactionGetter(
986+
void DatabaseSync::IsTransactionGetterSlow(
983987
const FunctionCallbackInfo<Value>& args) {
984988
DatabaseSync* db;
985989
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@@ -988,6 +992,23 @@ void DatabaseSync::IsTransactionGetter(
988992
args.GetReturnValue().Set(sqlite3_get_autocommit(db->connection_) == 0);
989993
}
990994

995+
bool DatabaseSync::IsTransactionGetterFast(
996+
Local<Value> receiver,
997+
Local<Object> holder,
998+
// NOLINTNEXTLINE(runtime/references) This is V8 api.
999+
FastApiCallbackOptions& options) {
1000+
TRACK_V8_FAST_API_CALL("DatabaseSync.isTransaction");
1001+
DatabaseSync* db;
1002+
ASSIGN_OR_RETURN_UNWRAP(&db, holder, false);
1003+
Environment* env = Environment::GetCurrent(options.isolate);
1004+
THROW_AND_RETURN_ON_BAD_STATE(
1005+
env, !db->IsOpen(), "database is not open", false);
1006+
return sqlite3_get_autocommit(db->connection_) == 0;
1007+
}
1008+
1009+
CFunction DatabaseSync::is_transaction_getter_methods[] = {
1010+
CFunction::Make(IsTransactionGetterFast)};
1011+
9911012
void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
9921013
DatabaseSync* db;
9931014
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@@ -2343,6 +2364,23 @@ static inline void SetSideEffectFreeGetter(
23432364
name, getter, Local<FunctionTemplate>(), DontDelete);
23442365
}
23452366

2367+
static inline void SetSideEffectFreeFastGetter(
2368+
Isolate* isolate,
2369+
Local<FunctionTemplate> class_template,
2370+
Local<String> name,
2371+
v8::FunctionCallback slow_callback,
2372+
const v8::CFunction* c_function) {
2373+
Local<v8::FunctionTemplate> getter =
2374+
NewFunctionTemplate(isolate,
2375+
slow_callback,
2376+
v8::Signature::New(isolate, class_template),
2377+
v8::ConstructorBehavior::kThrow,
2378+
v8::SideEffectType::kHasSideEffect,
2379+
c_function);
2380+
class_template->InstanceTemplate()->SetAccessorProperty(
2381+
name, getter, Local<FunctionTemplate>(), DontDelete);
2382+
}
2383+
23462384
Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
23472385
Environment* env) {
23482386
Local<FunctionTemplate> tmpl =
@@ -2672,10 +2710,11 @@ static void Initialize(Local<Object> target,
26722710
db_tmpl,
26732711
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
26742712
DatabaseSync::IsOpenGetter);
2675-
SetSideEffectFreeGetter(isolate,
2676-
db_tmpl,
2677-
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
2678-
DatabaseSync::IsTransactionGetter);
2713+
SetSideEffectFreeFastGetter(isolate,
2714+
db_tmpl,
2715+
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
2716+
DatabaseSync::IsTransactionGetterSlow,
2717+
DatabaseSync::is_transaction_getter_methods);
26792718
SetConstructorFunction(context, target, "DatabaseSync", db_tmpl);
26802719
SetConstructorFunction(context,
26812720
target,

src/node_sqlite.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "node_mem.h"
88
#include "sqlite3.h"
99
#include "util.h"
10+
#include "v8-fast-api-calls.h"
1011

1112
#include <map>
1213
#include <unordered_set>
@@ -61,8 +62,13 @@ class DatabaseSync : public BaseObject {
6162
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
6263
static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
6364
static void IsOpenGetter(const v8::FunctionCallbackInfo<v8::Value>& args);
64-
static void IsTransactionGetter(
65+
static void IsTransactionGetterSlow(
6566
const v8::FunctionCallbackInfo<v8::Value>& args);
67+
static bool IsTransactionGetterFast(
68+
v8::Local<v8::Value> receiver,
69+
v8::Local<v8::Object> holder,
70+
// NOLINTNEXTLINE(runtime/references) This is V8 api.
71+
v8::FastApiCallbackOptions& options);
6672
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
6773
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
6874
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -93,6 +99,8 @@ class DatabaseSync : public BaseObject {
9399
SET_MEMORY_INFO_NAME(DatabaseSync)
94100
SET_SELF_SIZE(DatabaseSync)
95101

102+
static v8::CFunction is_transaction_getter_methods[];
103+
96104
private:
97105
bool Open();
98106
void DeleteSessions();

src/util-local-vector.h

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
#ifndef SRC_UTIL_LOCAL_VECTOR_H_
23+
#define SRC_UTIL_LOCAL_VECTOR_H_
24+
25+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
26+
27+
#include "util.h"
28+
#include "v8.h"
29+
30+
namespace node {
31+
32+
// Template specialization for MaybeStackBuffer<v8::Local<T>> that uses
33+
// v8::LocalVector<T> for heap allocations instead of malloc/free.
34+
template <typename T, size_t kStackStorageSize>
35+
class MaybeStackBuffer<v8::Local<T>, kStackStorageSize> {
36+
public:
37+
// Disallow copy constructor
38+
MaybeStackBuffer(const MaybeStackBuffer&) = delete;
39+
// Disallow copy assignment operator
40+
MaybeStackBuffer& operator=(const MaybeStackBuffer& other) = delete;
41+
42+
const v8::Local<T>* out() const { return buf_; }
43+
44+
v8::Local<T>* out() { return buf_; }
45+
46+
// operator* for compatibility with `v8::String::(Utf8)Value`
47+
v8::Local<T>* operator*() { return buf_; }
48+
49+
const v8::Local<T>* operator*() const { return buf_; }
50+
51+
v8::Local<T>& operator[](size_t index) {
52+
CHECK_LT(index, length());
53+
return buf_[index];
54+
}
55+
56+
const v8::Local<T>& operator[](size_t index) const {
57+
CHECK_LT(index, length());
58+
return buf_[index];
59+
}
60+
61+
size_t length() const { return length_; }
62+
63+
// Current maximum capacity of the buffer with which SetLength() can be used
64+
// without first calling AllocateSufficientStorage().
65+
size_t capacity() const { return capacity_; }
66+
67+
// Make sure enough space for `storage` entries is available.
68+
// This method can be called multiple times throughout the lifetime of the
69+
// buffer, but once this has been called Invalidate() cannot be used.
70+
// Content of the buffer in the range [0, length()) is preserved.
71+
void AllocateSufficientStorage(size_t storage) {
72+
CHECK(!IsInvalidated());
73+
if (storage > capacity()) {
74+
bool was_allocated = IsAllocated();
75+
76+
if (was_allocated) {
77+
// Copy from existing LocalVector to a new one
78+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
79+
auto new_vec = std::make_unique<v8::LocalVector<T>>(isolate, storage);
80+
for (size_t i = 0; i < length_; i++) {
81+
(*new_vec)[i] = buf_[i];
82+
}
83+
vector_buf_ = std::move(new_vec);
84+
buf_ = vector_buf_->data();
85+
} else {
86+
// First allocation or switching from stack to heap
87+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
88+
vector_buf_ = std::make_unique<v8::LocalVector<T>>(isolate, storage);
89+
90+
// Copy from stack to LocalVector if needed
91+
if (length_ > 0) {
92+
for (size_t i = 0; i < length_; i++) {
93+
(*vector_buf_)[i] = buf_st_[i];
94+
}
95+
}
96+
buf_ = vector_buf_->data();
97+
}
98+
capacity_ = storage;
99+
}
100+
101+
length_ = storage;
102+
}
103+
104+
void SetLength(size_t length) {
105+
// capacity() returns how much memory is actually available.
106+
CHECK_LE(length, capacity());
107+
length_ = length;
108+
}
109+
110+
void SetLengthAndZeroTerminate(size_t length) {
111+
// capacity() returns how much memory is actually available.
112+
CHECK_LE(length + 1, capacity());
113+
SetLength(length);
114+
115+
// Set null for the last element
116+
buf_[length] = v8::Local<T>();
117+
}
118+
119+
// Make dereferencing this object return nullptr.
120+
// This method can be called multiple times throughout the lifetime of the
121+
// buffer, but once this has been called AllocateSufficientStorage() cannot
122+
// be used.
123+
void Invalidate() {
124+
CHECK(!IsAllocated());
125+
capacity_ = 0;
126+
length_ = 0;
127+
buf_ = nullptr;
128+
}
129+
130+
// If the buffer is stored in the heap rather than on the stack.
131+
bool IsAllocated() const { return !IsInvalidated() && buf_ != buf_st_; }
132+
133+
// If Invalidate() has been called.
134+
bool IsInvalidated() const { return buf_ == nullptr; }
135+
136+
// Release ownership of the LocalVector buffer.
137+
void Release() {
138+
CHECK(IsAllocated());
139+
vector_buf_.reset();
140+
buf_ = buf_st_;
141+
length_ = 0;
142+
capacity_ = arraysize(buf_st_);
143+
}
144+
145+
MaybeStackBuffer()
146+
: length_(0),
147+
capacity_(arraysize(buf_st_)),
148+
buf_(buf_st_),
149+
vector_buf_(nullptr) {
150+
// Default to a zero-length buffer.
151+
}
152+
153+
explicit MaybeStackBuffer(size_t storage) : MaybeStackBuffer() {
154+
AllocateSufficientStorage(storage);
155+
}
156+
157+
~MaybeStackBuffer() {
158+
// LocalVector manages its own memory
159+
vector_buf_.reset();
160+
}
161+
162+
private:
163+
size_t length_;
164+
// capacity of the vector_buf_ or stack buffer
165+
size_t capacity_;
166+
v8::Local<T>* buf_;
167+
v8::Local<T> buf_st_[kStackStorageSize];
168+
// Used when we allocate on the heap
169+
std::unique_ptr<v8::LocalVector<T>> vector_buf_;
170+
};
171+
172+
} // namespace node
173+
174+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
175+
176+
#endif // SRC_UTIL_LOCAL_VECTOR_H_

test/parallel/test-sqlite-database-sync.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// Flags: --expose-internals --allow-natives-syntax
12
'use strict';
2-
require('../common');
3+
const { isDebug } = require('../common');
34
const tmpdir = require('../common/tmpdir');
45
const { existsSync } = require('node:fs');
56
const { join } = require('node:path');
67
const { DatabaseSync, StatementSync } = require('node:sqlite');
78
const { suite, test } = require('node:test');
9+
const assert = require('node:assert');
10+
const { internalBinding } = require('internal/test/binding');
811
let cnt = 0;
912

1013
tmpdir.refresh();
@@ -360,6 +363,33 @@ suite('DatabaseSync.prototype.isTransaction', () => {
360363
message: /database is not open/,
361364
});
362365
});
366+
367+
test('supports isTransaction fast path', (t) => {
368+
const db = new DatabaseSync(':memory:');
369+
370+
// Only javascript methods can be optimized through %OptimizeFunctionOnNextCall
371+
// This is why we surround the C++ method we want to optimize with a JS function.
372+
function isTransaction() {
373+
return db.isTransaction;
374+
}
375+
376+
t.assert.strictEqual(db.isTransaction, false);
377+
db.exec('BEGIN');
378+
eval('%PrepareFunctionForOptimization(isTransaction)');
379+
t.assert.strictEqual(isTransaction(), true);
380+
eval('%OptimizeFunctionOnNextCall(isTransaction)');
381+
t.assert.strictEqual(isTransaction(), true);
382+
383+
if (isDebug) {
384+
const { getV8FastApiCallCount } = internalBinding('debug');
385+
assert.strictEqual(getV8FastApiCallCount('DatabaseSync.isTransaction'), 1);
386+
}
387+
388+
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
389+
t.assert.strictEqual(db.isTransaction, true);
390+
db.exec('COMMIT');
391+
t.assert.strictEqual(db.isTransaction, false);
392+
});
363393
});
364394

365395
suite('DatabaseSync.prototype.location()', () => {

0 commit comments

Comments
 (0)