mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
sqlite: add tagged template
This pr introduces the support for tagged templates And an LRU to cache the templates. We introduced a new object called SqlTagStore that holds the ref to Lru. This acts as the main object that allows us to use tagged templates. PR-URL: https://github.com/nodejs/node/pull/58748 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
This commit is contained in:
parent
65bee02cca
commit
97e55f8ea2
|
|
@ -361,6 +361,53 @@ added: v22.5.0
|
|||
Compiles a SQL statement into a [prepared statement][]. This method is a wrapper
|
||||
around [`sqlite3_prepare_v2()`][].
|
||||
|
||||
### `database.createSQLTagStore([maxSize])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `maxSize` {integer} The maximum number of prepared statements to cache.
|
||||
**Default:** `1000`.
|
||||
* Returns: {SQLTagStore} A new SQL tag store for caching prepared statements.
|
||||
|
||||
Creates a new `SQLTagStore`, which is an LRU (Least Recently Used) cache for
|
||||
storing prepared statements. This allows for the efficient reuse of prepared
|
||||
statements by tagging them with a unique identifier.
|
||||
|
||||
When a tagged SQL literal is executed, the `SQLTagStore` checks if a prepared
|
||||
statement for that specific SQL string already exists in the cache. If it does,
|
||||
the cached statement is used. If not, a new prepared statement is created,
|
||||
executed, and then stored in the cache for future use. This mechanism helps to
|
||||
avoid the overhead of repeatedly parsing and preparing the same SQL statements.
|
||||
|
||||
```mjs
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
|
||||
const db = new DatabaseSync(':memory:');
|
||||
const sql = db.createSQLTagStore();
|
||||
|
||||
db.exec('CREATE TABLE users (id INT, name TEXT)');
|
||||
|
||||
// Using the 'run' method to insert data.
|
||||
// The tagged literal is used to identify the prepared statement.
|
||||
sql.run`INSERT INTO users VALUES (1, 'Alice')`;
|
||||
sql.run`INSERT INTO users VALUES (2, 'Bob')`;
|
||||
|
||||
// Using the 'get' method to retrieve a single row.
|
||||
const id = 1;
|
||||
const user = sql.get`SELECT * FROM users WHERE id = ${id}`;
|
||||
console.log(user); // { id: 1, name: 'Alice' }
|
||||
|
||||
// Using the 'all' method to retrieve all rows.
|
||||
const allUsers = sql.all`SELECT * FROM users ORDER BY id`;
|
||||
console.log(allUsers);
|
||||
// [
|
||||
// { id: 1, name: 'Alice' },
|
||||
// { id: 2, name: 'Bob' }
|
||||
// ]
|
||||
```
|
||||
|
||||
### `database.createSession([options])`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -504,6 +551,120 @@ times with different bound values. Parameters also offer protection against
|
|||
[SQL injection][] attacks. For these reasons, prepared statements are preferred
|
||||
over hand-crafted SQL strings when handling user input.
|
||||
|
||||
## Class: `SQLTagStore`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
This class represents a single LRU (Least Recently Used) cache for storing
|
||||
prepared statements.
|
||||
|
||||
Instances of this class are created via the database.createSQLTagStore() method,
|
||||
not by using a constructor. The store caches prepared statements based on the
|
||||
provided SQL query string. When the same query is seen again, the store
|
||||
retrieves the cached statement and safely applies the new values through
|
||||
parameter binding, thereby preventing attacks like SQL injection.
|
||||
|
||||
The cache has a maxSize that defaults to 1000 statements, but a custom size can
|
||||
be provided (e.g., database.createSQLTagStore(100)). All APIs exposed by this
|
||||
class execute synchronously.
|
||||
|
||||
### `sqlTagStore.all(sqlTemplate[, ...values])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
|
||||
* `...values` {any} Values to be interpolated into the template literal.
|
||||
* Returns: {Array} An array of objects representing the rows returned by the query.
|
||||
|
||||
Executes the given SQL query and returns all resulting rows as an array of objects.
|
||||
|
||||
### `sqlTagStore.get(sqlTemplate[, ...values])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
|
||||
* `...values` {any} Values to be interpolated into the template literal.
|
||||
* Returns: {Object | undefined} An object representing the first row returned by
|
||||
the query, or `undefined` if no rows are returned.
|
||||
|
||||
Executes the given SQL query and returns the first resulting row as an object.
|
||||
|
||||
### `sqlTagStore.iterate(sqlTemplate[, ...values])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
|
||||
* `...values` {any} Values to be interpolated into the template literal.
|
||||
* Returns: {Iterator} An iterator that yields objects representing the rows returned by the query.
|
||||
|
||||
Executes the given SQL query and returns an iterator over the resulting rows.
|
||||
|
||||
### `sqlTagStore.run(sqlTemplate[, ...values])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
|
||||
* `...values` {any} Values to be interpolated into the template literal.
|
||||
* Returns: {Object} An object containing information about the execution, including `changes` and `lastInsertRowid`.
|
||||
|
||||
Executes the given SQL query, which is expected to not return any rows (e.g., INSERT, UPDATE, DELETE).
|
||||
|
||||
### `sqlTagStore.size()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns: {integer} The number of prepared statements currently in the cache.
|
||||
|
||||
A read-only property that returns the number of prepared statements currently in the cache.
|
||||
|
||||
### `sqlTagStore.capacity`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns: {integer} The maximum number of prepared statements the cache can hold.
|
||||
|
||||
A read-only property that returns the maximum number of prepared statements the cache can hold.
|
||||
|
||||
### `sqlTagStore.db`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {DatabaseSync} The `DatabaseSync` instance that created this `SQLTagStore`.
|
||||
|
||||
A read-only property that returns the `DatabaseSync` object associated with this `SQLTagStore`.
|
||||
|
||||
### `sqlTagStore.reset()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Resets the LRU cache, clearing all stored prepared statements.
|
||||
|
||||
### `sqlTagStore.clear()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
An alias for `sqlTagStore.reset()`.
|
||||
|
||||
### `statement.all([namedParameters][, ...anonymousParameters])`
|
||||
|
||||
<!-- YAML
|
||||
|
|
|
|||
69
src/lru_cache-inl.h
Normal file
69
src/lru_cache-inl.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef SRC_LRU_CACHE_INL_H_
|
||||
#define SRC_LRU_CACHE_INL_H_
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
template <typename key_t, typename value_t>
|
||||
class LRUCache {
|
||||
public:
|
||||
using key_value_pair_t = typename std::pair<key_t, value_t>;
|
||||
using iterator = typename std::list<key_value_pair_t>::iterator;
|
||||
using const_iterator = typename std::list<key_value_pair_t>::const_iterator;
|
||||
|
||||
const_iterator begin() const { return lru_list_.begin(); }
|
||||
const_iterator end() const { return lru_list_.end(); }
|
||||
|
||||
explicit LRUCache(size_t capacity) : capacity_(capacity) {}
|
||||
|
||||
void Put(const key_t& key, const value_t& value) {
|
||||
auto it = lookup_map_.find(key);
|
||||
if (it != lookup_map_.end()) {
|
||||
lru_list_.erase(it->second);
|
||||
lookup_map_.erase(it);
|
||||
}
|
||||
|
||||
lru_list_.push_front(std::make_pair(key, value));
|
||||
lookup_map_[key] = lru_list_.begin();
|
||||
|
||||
if (lookup_map_.size() > capacity_) {
|
||||
auto last = lru_list_.end();
|
||||
last--;
|
||||
lookup_map_.erase(last->first);
|
||||
lru_list_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
value_t& Get(const key_t& key) {
|
||||
auto it = lookup_map_.find(key);
|
||||
lru_list_.splice(lru_list_.begin(), lru_list_, it->second);
|
||||
return it->second->second;
|
||||
}
|
||||
|
||||
void Erase(const key_t& key) {
|
||||
auto it = lookup_map_.find(key);
|
||||
if (it != lookup_map_.end()) {
|
||||
lru_list_.erase(it->second);
|
||||
lookup_map_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool Exists(const key_t& key) const { return lookup_map_.count(key) > 0; }
|
||||
|
||||
size_t Size() const { return lookup_map_.size(); }
|
||||
|
||||
size_t Capacity() const { return capacity_; }
|
||||
|
||||
void Clear() {
|
||||
lru_list_.clear();
|
||||
lookup_map_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<key_value_pair_t> lru_list_;
|
||||
std::unordered_map<key_t, iterator> lookup_map_;
|
||||
size_t capacity_;
|
||||
};
|
||||
|
||||
#endif // SRC_LRU_CACHE_INL_H_
|
||||
|
|
@ -809,6 +809,29 @@ bool DatabaseSync::ShouldIgnoreSQLiteError() {
|
|||
return ignore_next_sqlite_error_;
|
||||
}
|
||||
|
||||
void DatabaseSync::CreateTagStore(const FunctionCallbackInfo<Value>& args) {
|
||||
DatabaseSync* db = BaseObject::Unwrap<DatabaseSync>(args.This());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
if (!db->IsOpen()) {
|
||||
THROW_ERR_INVALID_STATE(env, "database is not open");
|
||||
return;
|
||||
}
|
||||
int capacity = 1000;
|
||||
if (args.Length() > 0 && args[0]->IsNumber()) {
|
||||
capacity = args[0].As<Number>()->Value();
|
||||
}
|
||||
|
||||
BaseObjectPtr<SQLTagStore> session =
|
||||
SQLTagStore::Create(env, BaseObjectWeakPtr<DatabaseSync>(db), capacity);
|
||||
if (!session) {
|
||||
// Handle error if creation failed
|
||||
THROW_ERR_SQLITE_ERROR(env->isolate(), "Failed to create SQLTagStore");
|
||||
return;
|
||||
}
|
||||
args.GetReturnValue().Set(session->object());
|
||||
}
|
||||
|
||||
std::optional<std::string> ValidateDatabasePath(Environment* env,
|
||||
Local<Value> path,
|
||||
const std::string& field_name) {
|
||||
|
|
@ -2011,11 +2034,8 @@ bool StatementSync::BindValue(const Local<Value>& value, const int index) {
|
|||
}
|
||||
|
||||
MaybeLocal<Value> StatementSync::ColumnToValue(const int column) {
|
||||
Isolate* isolate = env()->isolate();
|
||||
MaybeLocal<Value> js_val = MaybeLocal<Value>();
|
||||
SQLITE_VALUE_TO_JS(
|
||||
column, isolate, use_big_ints_, js_val, statement_, column);
|
||||
return js_val;
|
||||
return StatementExecutionHelper::ColumnToValue(
|
||||
env(), statement_, column, use_big_ints_);
|
||||
}
|
||||
|
||||
MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
|
||||
|
|
@ -2028,22 +2048,203 @@ MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
|
|||
return String::NewFromUtf8(env()->isolate(), col_name).As<Name>();
|
||||
}
|
||||
|
||||
MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
|
||||
sqlite3_stmt* stmt,
|
||||
const int column,
|
||||
bool use_big_ints) {
|
||||
Isolate* isolate = env->isolate();
|
||||
MaybeLocal<Value> js_val = MaybeLocal<Value>();
|
||||
SQLITE_VALUE_TO_JS(column, isolate, use_big_ints, js_val, stmt, column);
|
||||
return js_val;
|
||||
}
|
||||
|
||||
MaybeLocal<Name> StatementExecutionHelper::ColumnNameToName(Environment* env,
|
||||
sqlite3_stmt* stmt,
|
||||
const int column) {
|
||||
const char* col_name = sqlite3_column_name(stmt, column);
|
||||
if (col_name == nullptr) {
|
||||
THROW_ERR_INVALID_STATE(env, "Cannot get name of column %d", column);
|
||||
return MaybeLocal<Name>();
|
||||
}
|
||||
|
||||
return String::NewFromUtf8(env->isolate(), col_name).As<Name>();
|
||||
}
|
||||
|
||||
void StatementSync::MemoryInfo(MemoryTracker* tracker) const {}
|
||||
|
||||
Maybe<void> ExtractRowValues(Isolate* isolate,
|
||||
Maybe<void> ExtractRowValues(Environment* env,
|
||||
sqlite3_stmt* stmt,
|
||||
int num_cols,
|
||||
StatementSync* stmt,
|
||||
bool use_big_ints,
|
||||
LocalVector<Value>* row_values) {
|
||||
row_values->clear();
|
||||
row_values->reserve(num_cols);
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
Local<Value> val;
|
||||
if (!stmt->ColumnToValue(i).ToLocal(&val)) return Nothing<void>();
|
||||
if (!StatementExecutionHelper::ColumnToValue(env, stmt, i, use_big_ints)
|
||||
.ToLocal(&val)) {
|
||||
return Nothing<void>();
|
||||
}
|
||||
row_values->emplace_back(val);
|
||||
}
|
||||
return JustVoid();
|
||||
}
|
||||
|
||||
Local<Value> StatementExecutionHelper::All(Environment* env,
|
||||
DatabaseSync* db,
|
||||
sqlite3_stmt* stmt,
|
||||
bool return_arrays,
|
||||
bool use_big_ints) {
|
||||
Isolate* isolate = env->isolate();
|
||||
int r;
|
||||
int num_cols = sqlite3_column_count(stmt);
|
||||
LocalVector<Value> rows(isolate);
|
||||
LocalVector<Value> row_values(isolate);
|
||||
LocalVector<Name> row_keys(isolate);
|
||||
|
||||
while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values)
|
||||
.IsNothing()) {
|
||||
return Undefined(isolate);
|
||||
}
|
||||
|
||||
if (return_arrays) {
|
||||
Local<Array> row_array =
|
||||
Array::New(isolate, row_values.data(), row_values.size());
|
||||
rows.emplace_back(row_array);
|
||||
} else {
|
||||
if (row_keys.size() == 0) {
|
||||
row_keys.reserve(num_cols);
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
Local<Name> key;
|
||||
if (!ColumnNameToName(env, stmt, i).ToLocal(&key))
|
||||
return Undefined(isolate);
|
||||
row_keys.emplace_back(key);
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(row_keys.size(), row_values.size());
|
||||
Local<Object> row_obj = Object::New(
|
||||
isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols);
|
||||
rows.emplace_back(row_obj);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_DONE, Undefined(isolate));
|
||||
return Array::New(isolate, rows.data(), rows.size());
|
||||
}
|
||||
|
||||
Local<Object> StatementExecutionHelper::Run(Environment* env,
|
||||
DatabaseSync* db,
|
||||
sqlite3_stmt* stmt,
|
||||
bool use_big_ints) {
|
||||
Isolate* isolate = env->isolate();
|
||||
sqlite3_step(stmt);
|
||||
int r = sqlite3_reset(stmt);
|
||||
CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, Object::New(isolate));
|
||||
Local<Object> result = Object::New(isolate);
|
||||
sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection());
|
||||
sqlite3_int64 changes = sqlite3_changes64(db->Connection());
|
||||
Local<Value> last_insert_rowid_val;
|
||||
Local<Value> changes_val;
|
||||
|
||||
if (use_big_ints) {
|
||||
last_insert_rowid_val = BigInt::New(isolate, last_insert_rowid);
|
||||
changes_val = BigInt::New(isolate, changes);
|
||||
} else {
|
||||
last_insert_rowid_val = Number::New(isolate, last_insert_rowid);
|
||||
changes_val = Number::New(isolate, changes);
|
||||
}
|
||||
|
||||
if (result
|
||||
->Set(env->context(),
|
||||
env->last_insert_rowid_string(),
|
||||
last_insert_rowid_val)
|
||||
.IsNothing() ||
|
||||
result->Set(env->context(), env->changes_string(), changes_val)
|
||||
.IsNothing()) {
|
||||
return Object::New(isolate);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BaseObjectPtr<StatementSyncIterator> StatementExecutionHelper::Iterate(
|
||||
Environment* env, BaseObjectPtr<StatementSync> stmt) {
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> global = context->Global();
|
||||
Local<Value> js_iterator;
|
||||
Local<Value> js_iterator_prototype;
|
||||
if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) {
|
||||
return BaseObjectPtr<StatementSyncIterator>();
|
||||
}
|
||||
if (!js_iterator.As<Object>()
|
||||
->Get(context, env->prototype_string())
|
||||
.ToLocal(&js_iterator_prototype)) {
|
||||
return BaseObjectPtr<StatementSyncIterator>();
|
||||
}
|
||||
|
||||
BaseObjectPtr<StatementSyncIterator> iter =
|
||||
StatementSyncIterator::Create(env, stmt);
|
||||
|
||||
if (!iter) {
|
||||
// Error in iterator creation, likely already threw in Create
|
||||
return BaseObjectPtr<StatementSyncIterator>();
|
||||
}
|
||||
|
||||
if (iter->object()
|
||||
->GetPrototypeV2()
|
||||
.As<Object>()
|
||||
->SetPrototypeV2(context, js_iterator_prototype)
|
||||
.IsNothing()) {
|
||||
return BaseObjectPtr<StatementSyncIterator>();
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
Local<Value> StatementExecutionHelper::Get(Environment* env,
|
||||
DatabaseSync* db,
|
||||
sqlite3_stmt* stmt,
|
||||
bool return_arrays,
|
||||
bool use_big_ints) {
|
||||
Isolate* isolate = env->isolate();
|
||||
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt); });
|
||||
|
||||
int r = sqlite3_step(stmt);
|
||||
if (r == SQLITE_DONE) return Undefined(isolate);
|
||||
if (r != SQLITE_ROW) {
|
||||
THROW_ERR_SQLITE_ERROR(isolate, db);
|
||||
return Undefined(isolate);
|
||||
}
|
||||
|
||||
int num_cols = sqlite3_column_count(stmt);
|
||||
if (num_cols == 0) {
|
||||
return Undefined(isolate);
|
||||
}
|
||||
|
||||
LocalVector<Value> row_values(isolate);
|
||||
if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values)
|
||||
.IsNothing()) {
|
||||
return Undefined(isolate);
|
||||
}
|
||||
|
||||
if (return_arrays) {
|
||||
return Array::New(isolate, row_values.data(), row_values.size());
|
||||
} else {
|
||||
LocalVector<Name> keys(isolate);
|
||||
keys.reserve(num_cols);
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
MaybeLocal<Name> key = ColumnNameToName(env, stmt, i);
|
||||
if (key.IsEmpty()) return Undefined(isolate);
|
||||
keys.emplace_back(key.ToLocalChecked());
|
||||
}
|
||||
|
||||
DCHECK_EQ(keys.size(), row_values.size());
|
||||
return Object::New(
|
||||
isolate, Null(isolate), keys.data(), row_values.data(), num_cols);
|
||||
}
|
||||
}
|
||||
|
||||
void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
|
||||
StatementSync* stmt;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
|
||||
|
|
@ -2059,39 +2260,11 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
|
|||
}
|
||||
|
||||
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
||||
int num_cols = sqlite3_column_count(stmt->statement_);
|
||||
LocalVector<Value> rows(isolate);
|
||||
LocalVector<Value> row_values(isolate);
|
||||
LocalVector<Name> row_keys(isolate);
|
||||
|
||||
while ((r = sqlite3_step(stmt->statement_)) == SQLITE_ROW) {
|
||||
auto maybe_row_values =
|
||||
ExtractRowValues(env->isolate(), num_cols, stmt, &row_values);
|
||||
if (maybe_row_values.IsNothing()) return;
|
||||
|
||||
if (stmt->return_arrays_) {
|
||||
Local<Array> row_array =
|
||||
Array::New(isolate, row_values.data(), row_values.size());
|
||||
rows.emplace_back(row_array);
|
||||
} else {
|
||||
if (row_keys.size() == 0) {
|
||||
row_keys.reserve(num_cols);
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
Local<Name> key;
|
||||
if (!stmt->ColumnNameToName(i).ToLocal(&key)) return;
|
||||
row_keys.emplace_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
DCHECK_EQ(row_keys.size(), row_values.size());
|
||||
Local<Object> row_obj = Object::New(
|
||||
isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols);
|
||||
rows.emplace_back(row_obj);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_DONE, void());
|
||||
args.GetReturnValue().Set(Array::New(isolate, rows.data(), rows.size()));
|
||||
args.GetReturnValue().Set(StatementExecutionHelper::All(env,
|
||||
stmt->db_.get(),
|
||||
stmt->statement_,
|
||||
stmt->return_arrays_,
|
||||
stmt->use_big_ints_));
|
||||
}
|
||||
|
||||
void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
|
||||
|
|
@ -2100,35 +2273,17 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
|
|||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, stmt->IsFinalized(), "statement has been finalized");
|
||||
auto isolate = env->isolate();
|
||||
auto context = env->context();
|
||||
int r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
|
||||
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
|
||||
|
||||
if (!stmt->BindParams(args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Object> global = context->Global();
|
||||
Local<Value> js_iterator;
|
||||
Local<Value> js_iterator_prototype;
|
||||
if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) {
|
||||
return;
|
||||
}
|
||||
if (!js_iterator.As<Object>()
|
||||
->Get(context, env->prototype_string())
|
||||
.ToLocal(&js_iterator_prototype)) {
|
||||
return;
|
||||
}
|
||||
BaseObjectPtr<StatementSyncIterator> iter = StatementExecutionHelper::Iterate(
|
||||
env, BaseObjectPtr<StatementSync>(stmt));
|
||||
|
||||
BaseObjectPtr<StatementSyncIterator> iter =
|
||||
StatementSyncIterator::Create(env, BaseObjectPtr<StatementSync>(stmt));
|
||||
|
||||
if (iter->object()
|
||||
->GetPrototypeV2()
|
||||
.As<Object>()
|
||||
->SetPrototypeV2(context, js_iterator_prototype)
|
||||
.IsNothing()) {
|
||||
if (!iter) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2141,59 +2296,18 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
|
|||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, stmt->IsFinalized(), "statement has been finalized");
|
||||
Isolate* isolate = env->isolate();
|
||||
int r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
|
||||
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
|
||||
|
||||
if (!stmt->BindParams(args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
||||
r = sqlite3_step(stmt->statement_);
|
||||
if (r == SQLITE_DONE) return;
|
||||
if (r != SQLITE_ROW) {
|
||||
THROW_ERR_SQLITE_ERROR(isolate, stmt->db_.get());
|
||||
return;
|
||||
}
|
||||
|
||||
int num_cols = sqlite3_column_count(stmt->statement_);
|
||||
if (num_cols == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stmt->return_arrays_) {
|
||||
LocalVector<Value> array_values(isolate);
|
||||
array_values.reserve(num_cols);
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
Local<Value> val;
|
||||
if (!stmt->ColumnToValue(i).ToLocal(&val)) return;
|
||||
array_values.emplace_back(val);
|
||||
}
|
||||
Local<Array> result =
|
||||
Array::New(isolate, array_values.data(), array_values.size());
|
||||
args.GetReturnValue().Set(result);
|
||||
} else {
|
||||
LocalVector<Name> keys(isolate);
|
||||
keys.reserve(num_cols);
|
||||
LocalVector<Value> values(isolate);
|
||||
values.reserve(num_cols);
|
||||
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
Local<Name> key;
|
||||
if (!stmt->ColumnNameToName(i).ToLocal(&key)) return;
|
||||
Local<Value> val;
|
||||
if (!stmt->ColumnToValue(i).ToLocal(&val)) return;
|
||||
keys.emplace_back(key);
|
||||
values.emplace_back(val);
|
||||
}
|
||||
|
||||
DCHECK_EQ(keys.size(), values.size());
|
||||
Local<Object> result = Object::New(
|
||||
isolate, Null(isolate), keys.data(), values.data(), num_cols);
|
||||
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
args.GetReturnValue().Set(StatementExecutionHelper::Get(env,
|
||||
stmt->db_.get(),
|
||||
stmt->statement_,
|
||||
stmt->return_arrays_,
|
||||
stmt->use_big_ints_));
|
||||
}
|
||||
|
||||
void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
|
||||
|
|
@ -2209,35 +2323,8 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
|
|||
return;
|
||||
}
|
||||
|
||||
sqlite3_step(stmt->statement_);
|
||||
r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
|
||||
Local<Object> result = Object::New(env->isolate());
|
||||
sqlite3_int64 last_insert_rowid =
|
||||
sqlite3_last_insert_rowid(stmt->db_->Connection());
|
||||
sqlite3_int64 changes = sqlite3_changes64(stmt->db_->Connection());
|
||||
Local<Value> last_insert_rowid_val;
|
||||
Local<Value> changes_val;
|
||||
|
||||
if (stmt->use_big_ints_) {
|
||||
last_insert_rowid_val = BigInt::New(env->isolate(), last_insert_rowid);
|
||||
changes_val = BigInt::New(env->isolate(), changes);
|
||||
} else {
|
||||
last_insert_rowid_val = Number::New(env->isolate(), last_insert_rowid);
|
||||
changes_val = Number::New(env->isolate(), changes);
|
||||
}
|
||||
|
||||
if (result
|
||||
->Set(env->context(),
|
||||
env->last_insert_rowid_string(),
|
||||
last_insert_rowid_val)
|
||||
.IsNothing() ||
|
||||
result->Set(env->context(), env->changes_string(), changes_val)
|
||||
.IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(result);
|
||||
args.GetReturnValue().Set(StatementExecutionHelper::Run(
|
||||
env, stmt->db_.get(), stmt->statement_, stmt->use_big_ints_));
|
||||
}
|
||||
|
||||
void StatementSync::Columns(const FunctionCallbackInfo<Value>& args) {
|
||||
|
|
@ -2390,6 +2477,17 @@ void IllegalConstructor(const FunctionCallbackInfo<Value>& args) {
|
|||
THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args));
|
||||
}
|
||||
|
||||
SQLTagStore::SQLTagStore(Environment* env,
|
||||
Local<Object> object,
|
||||
BaseObjectWeakPtr<DatabaseSync> database,
|
||||
int capacity)
|
||||
: BaseObject(env, object),
|
||||
database_(std::move(database)),
|
||||
sql_tags_(capacity),
|
||||
capacity_(capacity) {
|
||||
MakeWeak();
|
||||
}
|
||||
|
||||
static inline void SetSideEffectFreeGetter(
|
||||
Isolate* isolate,
|
||||
Local<FunctionTemplate> class_template,
|
||||
|
|
@ -2407,6 +2505,291 @@ static inline void SetSideEffectFreeGetter(
|
|||
name, getter, Local<FunctionTemplate>(), DontDelete);
|
||||
}
|
||||
|
||||
SQLTagStore::~SQLTagStore() {}
|
||||
|
||||
Local<FunctionTemplate> SQLTagStore::GetConstructorTemplate(Environment* env) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<FunctionTemplate> tmpl =
|
||||
NewFunctionTemplate(isolate, IllegalConstructor);
|
||||
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SQLTagStore"));
|
||||
tmpl->InstanceTemplate()->SetInternalFieldCount(
|
||||
SQLTagStore::kInternalFieldCount);
|
||||
SetProtoMethod(isolate, tmpl, "get", Get);
|
||||
SetProtoMethod(isolate, tmpl, "all", All);
|
||||
SetProtoMethod(isolate, tmpl, "iterate", Iterate);
|
||||
SetProtoMethod(isolate, tmpl, "run", Run);
|
||||
SetProtoMethod(isolate, tmpl, "clear", Clear);
|
||||
SetProtoMethod(isolate, tmpl, "size", Size);
|
||||
SetSideEffectFreeGetter(
|
||||
isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "capacity"), Capacity);
|
||||
SetSideEffectFreeGetter(
|
||||
isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "db"), DatabaseGetter);
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
BaseObjectPtr<SQLTagStore> SQLTagStore::Create(
|
||||
Environment* env, BaseObjectWeakPtr<DatabaseSync> database, int capacity) {
|
||||
Local<Object> obj;
|
||||
if (!GetConstructorTemplate(env)
|
||||
->InstanceTemplate()
|
||||
->NewInstance(env->context())
|
||||
.ToLocal(&obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
return MakeBaseObject<SQLTagStore>(env, obj, std::move(database), capacity);
|
||||
}
|
||||
|
||||
void SQLTagStore::DatabaseGetter(const FunctionCallbackInfo<Value>& args) {
|
||||
SQLTagStore* store;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&store, args.This());
|
||||
args.GetReturnValue().Set(store->database_->object());
|
||||
}
|
||||
|
||||
void SQLTagStore::Run(const FunctionCallbackInfo<Value>& info) {
|
||||
SQLTagStore* session;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&session, info.This());
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, !session->database_->IsOpen(), "database is not open");
|
||||
|
||||
BaseObjectPtr<StatementSync> stmt = PrepareStatement(info);
|
||||
|
||||
if (!stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t n_params = info.Length() - 1;
|
||||
int r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
|
||||
int param_count = sqlite3_bind_parameter_count(stmt->statement_);
|
||||
for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) {
|
||||
Local<Value> value = info[i + 1];
|
||||
if (!stmt->BindValue(value, i + 1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
info.GetReturnValue().Set(StatementExecutionHelper::Run(
|
||||
env, stmt->db_.get(), stmt->statement_, stmt->use_big_ints_));
|
||||
}
|
||||
|
||||
void SQLTagStore::Iterate(const FunctionCallbackInfo<Value>& args) {
|
||||
SQLTagStore* session;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, !session->database_->IsOpen(), "database is not open");
|
||||
|
||||
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
|
||||
|
||||
if (!stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t n_params = args.Length() - 1;
|
||||
int r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
|
||||
int param_count = sqlite3_bind_parameter_count(stmt->statement_);
|
||||
for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) {
|
||||
Local<Value> value = args[i + 1];
|
||||
if (!stmt->BindValue(value, i + 1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BaseObjectPtr<StatementSyncIterator> iter = StatementExecutionHelper::Iterate(
|
||||
env, BaseObjectPtr<StatementSync>(stmt));
|
||||
|
||||
if (!iter) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(iter->object());
|
||||
}
|
||||
|
||||
void SQLTagStore::Get(const FunctionCallbackInfo<Value>& args) {
|
||||
SQLTagStore* session;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, !session->database_->IsOpen(), "database is not open");
|
||||
|
||||
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
|
||||
|
||||
if (!stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t n_params = args.Length() - 1;
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
int r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
|
||||
|
||||
int param_count = sqlite3_bind_parameter_count(stmt->statement_);
|
||||
for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) {
|
||||
Local<Value> value = args[i + 1];
|
||||
if (!stmt->BindValue(value, i + 1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(StatementExecutionHelper::Get(env,
|
||||
stmt->db_.get(),
|
||||
stmt->statement_,
|
||||
stmt->return_arrays_,
|
||||
stmt->use_big_ints_));
|
||||
}
|
||||
|
||||
void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) {
|
||||
SQLTagStore* session;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, !session->database_->IsOpen(), "database is not open");
|
||||
|
||||
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
|
||||
|
||||
if (!stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t n_params = args.Length() - 1;
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
int r = sqlite3_reset(stmt->statement_);
|
||||
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
|
||||
|
||||
int param_count = sqlite3_bind_parameter_count(stmt->statement_);
|
||||
for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) {
|
||||
Local<Value> value = args[i + 1];
|
||||
if (!stmt->BindValue(value, i + 1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
||||
args.GetReturnValue().Set(StatementExecutionHelper::All(env,
|
||||
stmt->db_.get(),
|
||||
stmt->statement_,
|
||||
stmt->return_arrays_,
|
||||
stmt->use_big_ints_));
|
||||
}
|
||||
|
||||
void SQLTagStore::Size(const FunctionCallbackInfo<Value>& info) {
|
||||
SQLTagStore* store;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&store, info.This());
|
||||
info.GetReturnValue().Set(
|
||||
Integer::New(info.GetIsolate(), store->sql_tags_.Size()));
|
||||
}
|
||||
|
||||
void SQLTagStore::Capacity(const FunctionCallbackInfo<Value>& info) {
|
||||
SQLTagStore* store;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&store, info.This());
|
||||
info.GetReturnValue().Set(
|
||||
Integer::New(info.GetIsolate(), store->sql_tags_.Capacity()));
|
||||
}
|
||||
|
||||
void SQLTagStore::Clear(const FunctionCallbackInfo<Value>& info) {
|
||||
SQLTagStore* store;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&store, info.This());
|
||||
store->sql_tags_.Clear();
|
||||
}
|
||||
|
||||
BaseObjectPtr<StatementSync> SQLTagStore::PrepareStatement(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
SQLTagStore* session = BaseObject::FromJSObject<SQLTagStore>(args.This());
|
||||
if (!session) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(
|
||||
Environment::GetCurrent(args)->isolate(),
|
||||
"This method can only be called on SQLTagStore instances.");
|
||||
return BaseObjectPtr<StatementSync>();
|
||||
}
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() < 1 || !args[0]->IsArray()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(
|
||||
isolate,
|
||||
"First argument must be an array of strings (template literal).");
|
||||
return BaseObjectPtr<StatementSync>();
|
||||
}
|
||||
|
||||
Local<Array> strings = args[0].As<Array>();
|
||||
uint32_t n_strings = strings->Length();
|
||||
uint32_t n_params = args.Length() - 1;
|
||||
|
||||
std::string sql;
|
||||
for (uint32_t i = 0; i < n_strings; ++i) {
|
||||
Local<Value> str_val;
|
||||
if (!strings->Get(context, i).ToLocal(&str_val) || !str_val->IsString()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(isolate,
|
||||
"Template literal parts must be strings.");
|
||||
return BaseObjectPtr<StatementSync>();
|
||||
}
|
||||
Utf8Value part(isolate, str_val);
|
||||
sql += *part;
|
||||
if (i < n_params) {
|
||||
sql += "?";
|
||||
}
|
||||
}
|
||||
|
||||
BaseObjectPtr<StatementSync> stmt = nullptr;
|
||||
if (session->sql_tags_.Exists(sql)) {
|
||||
stmt = session->sql_tags_.Get(sql);
|
||||
if (stmt->IsFinalized()) {
|
||||
session->sql_tags_.Erase(sql);
|
||||
stmt = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (stmt == nullptr) {
|
||||
sqlite3_stmt* s = nullptr;
|
||||
Local<String> sql_str =
|
||||
String::NewFromUtf8(isolate, sql.c_str()).ToLocalChecked();
|
||||
Utf8Value sql_utf8(isolate, sql_str);
|
||||
|
||||
int r = sqlite3_prepare_v2(
|
||||
session->database_->connection_, *sql_utf8, -1, &s, 0);
|
||||
|
||||
if (r != SQLITE_OK) {
|
||||
THROW_ERR_SQLITE_ERROR(isolate, "Failed to prepare statement");
|
||||
sqlite3_finalize(s);
|
||||
return BaseObjectPtr<StatementSync>();
|
||||
}
|
||||
|
||||
BaseObjectPtr<StatementSync> stmt_obj = StatementSync::Create(
|
||||
env, BaseObjectPtr<DatabaseSync>(session->database_), s);
|
||||
|
||||
if (!stmt_obj) {
|
||||
THROW_ERR_SQLITE_ERROR(isolate, "Failed to create StatementSync");
|
||||
sqlite3_finalize(s);
|
||||
return BaseObjectPtr<StatementSync>();
|
||||
}
|
||||
|
||||
session->sql_tags_.Put(sql, stmt_obj);
|
||||
stmt = stmt_obj;
|
||||
}
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
void SQLTagStore::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackFieldWithSize(MemoryInfoName(), SelfSize());
|
||||
tracker->TrackField("database", database_);
|
||||
size_t cache_content_size = 0;
|
||||
for (const auto& pair : sql_tags_) {
|
||||
cache_content_size += pair.first.capacity();
|
||||
cache_content_size += sizeof(pair.second);
|
||||
}
|
||||
tracker->TrackFieldWithSize("sql_tags_cache", cache_content_size);
|
||||
}
|
||||
|
||||
Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
|
||||
Environment* env) {
|
||||
Local<FunctionTemplate> tmpl =
|
||||
|
|
@ -2544,9 +2927,14 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
|
|||
LocalVector<Name> row_keys(isolate);
|
||||
LocalVector<Value> row_values(isolate);
|
||||
|
||||
auto maybe_row_values =
|
||||
ExtractRowValues(isolate, num_cols, iter->stmt_.get(), &row_values);
|
||||
if (maybe_row_values.IsNothing()) return;
|
||||
if (ExtractRowValues(env,
|
||||
iter->stmt_->statement_,
|
||||
num_cols,
|
||||
iter->stmt_->use_big_ints_,
|
||||
&row_values)
|
||||
.IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (iter->stmt_->return_arrays_) {
|
||||
row_value = Array::New(isolate, row_values.data(), row_values.size());
|
||||
|
|
@ -2727,6 +3115,8 @@ static void Initialize(Local<Object> target,
|
|||
SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare);
|
||||
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
|
||||
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
|
||||
SetProtoMethod(
|
||||
isolate, db_tmpl, "createTagStore", DatabaseSync::CreateTagStore);
|
||||
SetProtoMethodNoSideEffect(
|
||||
isolate, db_tmpl, "location", DatabaseSync::Location);
|
||||
SetProtoMethod(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include "base_object.h"
|
||||
#include "lru_cache-inl.h"
|
||||
#include "node_mem.h"
|
||||
#include "sqlite3.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
|
|
@ -75,9 +77,38 @@ class DatabaseOpenConfiguration {
|
|||
bool allow_unknown_named_params_ = false;
|
||||
};
|
||||
|
||||
class DatabaseSync;
|
||||
class StatementSyncIterator;
|
||||
class StatementSync;
|
||||
class BackupJob;
|
||||
|
||||
class StatementExecutionHelper {
|
||||
public:
|
||||
static v8::Local<v8::Value> All(Environment* env,
|
||||
DatabaseSync* db,
|
||||
sqlite3_stmt* stmt,
|
||||
bool return_arrays,
|
||||
bool use_big_ints);
|
||||
static v8::Local<v8::Object> Run(Environment* env,
|
||||
DatabaseSync* db,
|
||||
sqlite3_stmt* stmt,
|
||||
bool use_big_ints);
|
||||
static BaseObjectPtr<StatementSyncIterator> Iterate(
|
||||
Environment* env, BaseObjectPtr<StatementSync> stmt);
|
||||
static v8::MaybeLocal<v8::Value> ColumnToValue(Environment* env,
|
||||
sqlite3_stmt* stmt,
|
||||
const int column,
|
||||
bool use_big_ints);
|
||||
static v8::MaybeLocal<v8::Name> ColumnNameToName(Environment* env,
|
||||
sqlite3_stmt* stmt,
|
||||
const int column);
|
||||
static v8::Local<v8::Value> Get(Environment* env,
|
||||
DatabaseSync* db,
|
||||
sqlite3_stmt* stmt,
|
||||
bool return_arrays,
|
||||
bool use_big_ints);
|
||||
};
|
||||
|
||||
class DatabaseSync : public BaseObject {
|
||||
public:
|
||||
DatabaseSync(Environment* env,
|
||||
|
|
@ -95,6 +126,7 @@ class DatabaseSync : public BaseObject {
|
|||
static void Dispose(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void CreateTagStore(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Location(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void CustomFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void AggregateFunction(
|
||||
|
|
@ -146,6 +178,8 @@ class DatabaseSync : public BaseObject {
|
|||
std::unordered_set<StatementSync*> statements_;
|
||||
|
||||
friend class Session;
|
||||
friend class SQLTagStore;
|
||||
friend class StatementExecutionHelper;
|
||||
};
|
||||
|
||||
class StatementSync : public BaseObject {
|
||||
|
|
@ -195,6 +229,8 @@ class StatementSync : public BaseObject {
|
|||
bool BindValue(const v8::Local<v8::Value>& value, const int index);
|
||||
|
||||
friend class StatementSyncIterator;
|
||||
friend class SQLTagStore;
|
||||
friend class StatementExecutionHelper;
|
||||
};
|
||||
|
||||
class StatementSyncIterator : public BaseObject {
|
||||
|
|
@ -248,6 +284,39 @@ class Session : public BaseObject {
|
|||
BaseObjectWeakPtr<DatabaseSync> database_; // The Parent Database
|
||||
};
|
||||
|
||||
class SQLTagStore : public BaseObject {
|
||||
public:
|
||||
SQLTagStore(Environment* env,
|
||||
v8::Local<v8::Object> object,
|
||||
BaseObjectWeakPtr<DatabaseSync> database,
|
||||
int capacity);
|
||||
~SQLTagStore() override;
|
||||
static BaseObjectPtr<SQLTagStore> Create(
|
||||
Environment* env, BaseObjectWeakPtr<DatabaseSync> database, int capacity);
|
||||
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
|
||||
Environment* env);
|
||||
static void All(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Get(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Iterate(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Run(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Size(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Capacity(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Reset(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void Clear(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void DatabaseGetter(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||
SET_MEMORY_INFO_NAME(SQLTagStore)
|
||||
SET_SELF_SIZE(SQLTagStore)
|
||||
|
||||
private:
|
||||
static BaseObjectPtr<StatementSync> PrepareStatement(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
BaseObjectWeakPtr<DatabaseSync> database_;
|
||||
LRUCache<std::string, BaseObjectPtr<StatementSync>> sql_tags_;
|
||||
int capacity_;
|
||||
friend class StatementExecutionHelper;
|
||||
};
|
||||
|
||||
class UserDefinedFunction {
|
||||
public:
|
||||
UserDefinedFunction(Environment* env,
|
||||
|
|
|
|||
155
test/cctest/test_lru_cache.cc
Normal file
155
test/cctest/test_lru_cache.cc
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include "gtest/gtest.h"
|
||||
#include "lru_cache-inl.h"
|
||||
#include "node_internals.h"
|
||||
|
||||
// Test basic Put and Get operations
|
||||
TEST(LRUCache, PutAndGet) {
|
||||
LRUCache<int, std::string> cache(2);
|
||||
cache.Put(1, "one");
|
||||
cache.Put(2, "two");
|
||||
|
||||
EXPECT_TRUE(cache.Exists(1));
|
||||
EXPECT_EQ(cache.Get(1), "one");
|
||||
|
||||
EXPECT_TRUE(cache.Exists(2));
|
||||
EXPECT_EQ(cache.Get(2), "two");
|
||||
|
||||
EXPECT_FALSE(cache.Exists(3));
|
||||
}
|
||||
|
||||
// Test that Putting an existing key updates its value and moves it to the front
|
||||
TEST(LRUCache, PutUpdatesExisting) {
|
||||
LRUCache<int, std::string> cache(2);
|
||||
cache.Put(1, "one");
|
||||
cache.Put(2, "two");
|
||||
cache.Put(1, "updated one");
|
||||
|
||||
EXPECT_EQ(cache.Size(), 2u);
|
||||
EXPECT_EQ(cache.Get(1), "updated one");
|
||||
|
||||
// Now, if we add another element, key 2 should be evicted, not key 1
|
||||
cache.Put(3, "three");
|
||||
EXPECT_FALSE(cache.Exists(2));
|
||||
EXPECT_TRUE(cache.Exists(1));
|
||||
EXPECT_TRUE(cache.Exists(3));
|
||||
}
|
||||
|
||||
// Test the eviction of the least recently used item
|
||||
TEST(LRUCache, Eviction) {
|
||||
LRUCache<int, int> cache(3);
|
||||
cache.Put(1, 10);
|
||||
cache.Put(2, 20);
|
||||
cache.Put(3, 30);
|
||||
|
||||
// At this point, the order of use is 3, 2, 1
|
||||
cache.Put(4, 40); // This should evict key 1
|
||||
|
||||
EXPECT_EQ(cache.Size(), 3u);
|
||||
EXPECT_FALSE(cache.Exists(1));
|
||||
EXPECT_TRUE(cache.Exists(2));
|
||||
EXPECT_TRUE(cache.Exists(3));
|
||||
EXPECT_TRUE(cache.Exists(4));
|
||||
}
|
||||
|
||||
// Test that Get() moves an item to the front (most recently used)
|
||||
TEST(LRUCache, GetMovesToFront) {
|
||||
LRUCache<char, int> cache(2);
|
||||
cache.Put('a', 1);
|
||||
cache.Put('b', 2);
|
||||
|
||||
// Access 'a', making it the most recently used
|
||||
cache.Get('a');
|
||||
|
||||
// Add 'c', which should evict 'b'
|
||||
cache.Put('c', 3);
|
||||
|
||||
EXPECT_EQ(cache.Size(), 2u);
|
||||
EXPECT_TRUE(cache.Exists('a'));
|
||||
EXPECT_FALSE(cache.Exists('b'));
|
||||
EXPECT_TRUE(cache.Exists('c'));
|
||||
}
|
||||
|
||||
// Test the Erase() method
|
||||
TEST(LRUCache, Erase) {
|
||||
LRUCache<int, std::string> cache(2);
|
||||
cache.Put(1, "one");
|
||||
cache.Put(2, "two");
|
||||
|
||||
cache.Erase(1);
|
||||
|
||||
EXPECT_EQ(cache.Size(), 1u);
|
||||
EXPECT_FALSE(cache.Exists(1));
|
||||
EXPECT_TRUE(cache.Exists(2));
|
||||
|
||||
// Erasing a non-existent key should not fail
|
||||
cache.Erase(99);
|
||||
EXPECT_EQ(cache.Size(), 1u);
|
||||
}
|
||||
|
||||
// Test the Exists() method
|
||||
TEST(LRUCache, Exists) {
|
||||
LRUCache<int, int> cache(1);
|
||||
cache.Put(1, 100);
|
||||
|
||||
EXPECT_TRUE(cache.Exists(1));
|
||||
EXPECT_FALSE(cache.Exists(2));
|
||||
}
|
||||
|
||||
// Test the Size() method
|
||||
TEST(LRUCache, Size) {
|
||||
LRUCache<int, int> cache(5);
|
||||
EXPECT_EQ(cache.Size(), 0u);
|
||||
|
||||
cache.Put(1, 1);
|
||||
EXPECT_EQ(cache.Size(), 1u);
|
||||
cache.Put(2, 2);
|
||||
EXPECT_EQ(cache.Size(), 2u);
|
||||
|
||||
cache.Put(1, 11); // Update
|
||||
EXPECT_EQ(cache.Size(), 2u);
|
||||
|
||||
cache.Erase(2);
|
||||
EXPECT_EQ(cache.Size(), 1u);
|
||||
}
|
||||
|
||||
// Test with a max_Size of 0
|
||||
TEST(LRUCache, ZeroSizeCache) {
|
||||
LRUCache<int, int> cache(0);
|
||||
cache.Put(1, 1);
|
||||
EXPECT_FALSE(cache.Exists(1));
|
||||
EXPECT_EQ(cache.Size(), 0u);
|
||||
}
|
||||
|
||||
// Test with a max_Size of 1
|
||||
TEST(LRUCache, OneSizeCache) {
|
||||
LRUCache<int, int> cache(1);
|
||||
cache.Put(1, 1);
|
||||
EXPECT_TRUE(cache.Exists(1));
|
||||
EXPECT_EQ(cache.Size(), 1u);
|
||||
|
||||
cache.Put(2, 2);
|
||||
EXPECT_FALSE(cache.Exists(1));
|
||||
EXPECT_TRUE(cache.Exists(2));
|
||||
EXPECT_EQ(cache.Size(), 1u);
|
||||
}
|
||||
|
||||
// Test with complex key and value types
|
||||
TEST(LRUCache, ComplexTypes) {
|
||||
LRUCache<std::string, std::vector<int>> cache(2);
|
||||
std::vector<int> vec1 = {1, 2, 3};
|
||||
std::vector<int> vec2 = {4, 5, 6};
|
||||
std::vector<int> vec3 = {7, 8, 9};
|
||||
|
||||
cache.Put("vec1", vec1);
|
||||
cache.Put("vec2", vec2);
|
||||
|
||||
EXPECT_EQ(cache.Get("vec1"), vec1);
|
||||
EXPECT_EQ(cache.Get("vec2"), vec2);
|
||||
|
||||
cache.Put("vec3", vec3);
|
||||
EXPECT_FALSE(cache.Exists("vec1"));
|
||||
EXPECT_TRUE(cache.Exists("vec2"));
|
||||
EXPECT_TRUE(cache.Exists("vec3"));
|
||||
}
|
||||
102
test/parallel/test-sqlite-template-tag.js
Normal file
102
test/parallel/test-sqlite-template-tag.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const { test, beforeEach } = require('node:test');
|
||||
|
||||
const db = new DatabaseSync(':memory:');
|
||||
const sql = db.createTagStore(10);
|
||||
|
||||
beforeEach(() => {
|
||||
db.exec('DROP TABLE IF EXISTS foo');
|
||||
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY, text TEXT)');
|
||||
sql.clear();
|
||||
});
|
||||
|
||||
test('sql.run inserts data', () => {
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'bob'})`.changes, 1);
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'mac'})`.changes, 1);
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'alice'})`.changes, 1);
|
||||
|
||||
const count = db.prepare('SELECT COUNT(*) as count FROM foo').get().count;
|
||||
assert.strictEqual(count, 3);
|
||||
});
|
||||
|
||||
test('sql.get retrieves a single row', () => {
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'bob'})`.changes, 1);
|
||||
const first = sql.get`SELECT * FROM foo ORDER BY id ASC`;
|
||||
assert.ok(first);
|
||||
assert.strictEqual(first.text, 'bob');
|
||||
assert.strictEqual(first.id, 1);
|
||||
assert.strictEqual(Object.getPrototypeOf(first), null);
|
||||
});
|
||||
|
||||
test('sql.all retrieves all rows', () => {
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'bob'})`.changes, 1);
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'mac'})`.changes, 1);
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'alice'})`.changes, 1);
|
||||
|
||||
const all = sql.all`SELECT * FROM foo ORDER BY id ASC`;
|
||||
assert.strictEqual(Array.isArray(all), true);
|
||||
assert.strictEqual(all.length, 3);
|
||||
for (const row of all) {
|
||||
assert.strictEqual(Object.getPrototypeOf(row), null);
|
||||
}
|
||||
assert.deepStrictEqual(all.map((r) => r.text), ['bob', 'mac', 'alice']);
|
||||
});
|
||||
|
||||
test('sql.iterate retrieves rows via iterator', () => {
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'bob'})`.changes, 1);
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'mac'})`.changes, 1);
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'alice'})`.changes, 1);
|
||||
|
||||
const iter = sql.iterate`SELECT * FROM foo ORDER BY id ASC`;
|
||||
const iterRows = [];
|
||||
for (const row of iter) {
|
||||
assert.ok(row);
|
||||
assert.strictEqual(Object.getPrototypeOf(row), null);
|
||||
iterRows.push(row.text);
|
||||
}
|
||||
assert.deepStrictEqual(iterRows, ['bob', 'mac', 'alice']);
|
||||
});
|
||||
|
||||
test('queries with no results', () => {
|
||||
const none = sql.get`SELECT * FROM foo WHERE text = ${'notfound'}`;
|
||||
assert.strictEqual(none, undefined);
|
||||
|
||||
const empty = sql.all`SELECT * FROM foo WHERE text = ${'notfound'}`;
|
||||
assert.deepStrictEqual(empty, []);
|
||||
|
||||
let count = 0;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (const _ of sql.iterate`SELECT * FROM foo WHERE text = ${'notfound'}`) {
|
||||
count++;
|
||||
}
|
||||
assert.strictEqual(count, 0);
|
||||
});
|
||||
|
||||
test('TagStore capacity, size, and clear', () => {
|
||||
assert.strictEqual(sql.capacity, 10);
|
||||
assert.strictEqual(sql.size(), 0);
|
||||
|
||||
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'one'})`.changes, 1);
|
||||
assert.strictEqual(sql.size(), 1);
|
||||
|
||||
assert.ok(sql.get`SELECT * FROM foo WHERE text = ${'one'}`);
|
||||
assert.strictEqual(sql.size(), 2);
|
||||
|
||||
// Using the same template string shouldn't increase the size
|
||||
assert.strictEqual(sql.get`SELECT * FROM foo WHERE text = ${'two'}`, undefined);
|
||||
assert.strictEqual(sql.size(), 2);
|
||||
|
||||
assert.strictEqual(sql.all`SELECT * FROM foo`.length, 1);
|
||||
assert.strictEqual(sql.size(), 3);
|
||||
|
||||
sql.clear();
|
||||
assert.strictEqual(sql.size(), 0);
|
||||
assert.strictEqual(sql.capacity, 10);
|
||||
});
|
||||
|
||||
test('sql.db returns the associated DatabaseSync instance', () => {
|
||||
assert.strictEqual(sql.db, db);
|
||||
});
|
||||
|
|
@ -96,6 +96,8 @@ const customTypesMap = {
|
|||
'EncapsulatedBits': 'webcrypto.html#class-encapsulatedbits',
|
||||
'EncapsulatedKey': 'webcrypto.html#class-encapsulatedkey',
|
||||
'SubtleCrypto': 'webcrypto.html#class-subtlecrypto',
|
||||
'Template Literal':
|
||||
`${jsDocPrefix}Reference/Template_literals`,
|
||||
'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams',
|
||||
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
|
||||
'AesCbcParams': 'webcrypto.html#class-aescbcparams',
|
||||
|
|
@ -234,6 +236,7 @@ const customTypesMap = {
|
|||
|
||||
'Session': 'sqlite.html#class-session',
|
||||
'StatementSync': 'sqlite.html#class-statementsync',
|
||||
'SQLTagStore': 'sqlite.html#class-sqltagstore',
|
||||
|
||||
'Stream': 'stream.html#stream',
|
||||
'stream.Duplex': 'stream.html#class-streamduplex',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user