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
|
Compiles a SQL statement into a [prepared statement][]. This method is a wrapper
|
||||||
around [`sqlite3_prepare_v2()`][].
|
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])`
|
### `database.createSession([options])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- 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
|
[SQL injection][] attacks. For these reasons, prepared statements are preferred
|
||||||
over hand-crafted SQL strings when handling user input.
|
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])`
|
### `statement.all([namedParameters][, ...anonymousParameters])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- 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_;
|
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,
|
std::optional<std::string> ValidateDatabasePath(Environment* env,
|
||||||
Local<Value> path,
|
Local<Value> path,
|
||||||
const std::string& field_name) {
|
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) {
|
MaybeLocal<Value> StatementSync::ColumnToValue(const int column) {
|
||||||
Isolate* isolate = env()->isolate();
|
return StatementExecutionHelper::ColumnToValue(
|
||||||
MaybeLocal<Value> js_val = MaybeLocal<Value>();
|
env(), statement_, column, use_big_ints_);
|
||||||
SQLITE_VALUE_TO_JS(
|
|
||||||
column, isolate, use_big_ints_, js_val, statement_, column);
|
|
||||||
return js_val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
|
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>();
|
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 {}
|
void StatementSync::MemoryInfo(MemoryTracker* tracker) const {}
|
||||||
|
|
||||||
Maybe<void> ExtractRowValues(Isolate* isolate,
|
Maybe<void> ExtractRowValues(Environment* env,
|
||||||
|
sqlite3_stmt* stmt,
|
||||||
int num_cols,
|
int num_cols,
|
||||||
StatementSync* stmt,
|
bool use_big_ints,
|
||||||
LocalVector<Value>* row_values) {
|
LocalVector<Value>* row_values) {
|
||||||
row_values->clear();
|
row_values->clear();
|
||||||
row_values->reserve(num_cols);
|
row_values->reserve(num_cols);
|
||||||
for (int i = 0; i < num_cols; ++i) {
|
for (int i = 0; i < num_cols; ++i) {
|
||||||
Local<Value> val;
|
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);
|
row_values->emplace_back(val);
|
||||||
}
|
}
|
||||||
return JustVoid();
|
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) {
|
void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
|
||||||
StatementSync* stmt;
|
StatementSync* stmt;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
|
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_); });
|
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
||||||
int num_cols = sqlite3_column_count(stmt->statement_);
|
args.GetReturnValue().Set(StatementExecutionHelper::All(env,
|
||||||
LocalVector<Value> rows(isolate);
|
stmt->db_.get(),
|
||||||
LocalVector<Value> row_values(isolate);
|
stmt->statement_,
|
||||||
LocalVector<Name> row_keys(isolate);
|
stmt->return_arrays_,
|
||||||
|
stmt->use_big_ints_));
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
|
void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
|
@ -2100,35 +2273,17 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
THROW_AND_RETURN_ON_BAD_STATE(
|
THROW_AND_RETURN_ON_BAD_STATE(
|
||||||
env, stmt->IsFinalized(), "statement has been finalized");
|
env, stmt->IsFinalized(), "statement has been finalized");
|
||||||
auto isolate = env->isolate();
|
|
||||||
auto context = env->context();
|
|
||||||
int r = sqlite3_reset(stmt->statement_);
|
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)) {
|
if (!stmt->BindParams(args)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Local<Object> global = context->Global();
|
BaseObjectPtr<StatementSyncIterator> iter = StatementExecutionHelper::Iterate(
|
||||||
Local<Value> js_iterator;
|
env, BaseObjectPtr<StatementSync>(stmt));
|
||||||
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 =
|
if (!iter) {
|
||||||
StatementSyncIterator::Create(env, BaseObjectPtr<StatementSync>(stmt));
|
|
||||||
|
|
||||||
if (iter->object()
|
|
||||||
->GetPrototypeV2()
|
|
||||||
.As<Object>()
|
|
||||||
->SetPrototypeV2(context, js_iterator_prototype)
|
|
||||||
.IsNothing()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2141,59 +2296,18 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
THROW_AND_RETURN_ON_BAD_STATE(
|
THROW_AND_RETURN_ON_BAD_STATE(
|
||||||
env, stmt->IsFinalized(), "statement has been finalized");
|
env, stmt->IsFinalized(), "statement has been finalized");
|
||||||
Isolate* isolate = env->isolate();
|
|
||||||
int r = sqlite3_reset(stmt->statement_);
|
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)) {
|
if (!stmt->BindParams(args)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
args.GetReturnValue().Set(StatementExecutionHelper::Get(env,
|
||||||
r = sqlite3_step(stmt->statement_);
|
stmt->db_.get(),
|
||||||
if (r == SQLITE_DONE) return;
|
stmt->statement_,
|
||||||
if (r != SQLITE_ROW) {
|
stmt->return_arrays_,
|
||||||
THROW_ERR_SQLITE_ERROR(isolate, stmt->db_.get());
|
stmt->use_big_ints_));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
|
void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
|
@ -2209,35 +2323,8 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_step(stmt->statement_);
|
args.GetReturnValue().Set(StatementExecutionHelper::Run(
|
||||||
r = sqlite3_reset(stmt->statement_);
|
env, stmt->db_.get(), stmt->statement_, stmt->use_big_ints_));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatementSync::Columns(const FunctionCallbackInfo<Value>& args) {
|
void StatementSync::Columns(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
|
@ -2390,6 +2477,17 @@ void IllegalConstructor(const FunctionCallbackInfo<Value>& args) {
|
||||||
THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(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(
|
static inline void SetSideEffectFreeGetter(
|
||||||
Isolate* isolate,
|
Isolate* isolate,
|
||||||
Local<FunctionTemplate> class_template,
|
Local<FunctionTemplate> class_template,
|
||||||
|
|
@ -2407,6 +2505,291 @@ static inline void SetSideEffectFreeGetter(
|
||||||
name, getter, Local<FunctionTemplate>(), DontDelete);
|
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(
|
Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
|
||||||
Environment* env) {
|
Environment* env) {
|
||||||
Local<FunctionTemplate> tmpl =
|
Local<FunctionTemplate> tmpl =
|
||||||
|
|
@ -2544,9 +2927,14 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
|
||||||
LocalVector<Name> row_keys(isolate);
|
LocalVector<Name> row_keys(isolate);
|
||||||
LocalVector<Value> row_values(isolate);
|
LocalVector<Value> row_values(isolate);
|
||||||
|
|
||||||
auto maybe_row_values =
|
if (ExtractRowValues(env,
|
||||||
ExtractRowValues(isolate, num_cols, iter->stmt_.get(), &row_values);
|
iter->stmt_->statement_,
|
||||||
if (maybe_row_values.IsNothing()) return;
|
num_cols,
|
||||||
|
iter->stmt_->use_big_ints_,
|
||||||
|
&row_values)
|
||||||
|
.IsNothing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (iter->stmt_->return_arrays_) {
|
if (iter->stmt_->return_arrays_) {
|
||||||
row_value = Array::New(isolate, row_values.data(), row_values.size());
|
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, "prepare", DatabaseSync::Prepare);
|
||||||
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
|
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
|
||||||
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
|
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
|
||||||
|
SetProtoMethod(
|
||||||
|
isolate, db_tmpl, "createTagStore", DatabaseSync::CreateTagStore);
|
||||||
SetProtoMethodNoSideEffect(
|
SetProtoMethodNoSideEffect(
|
||||||
isolate, db_tmpl, "location", DatabaseSync::Location);
|
isolate, db_tmpl, "location", DatabaseSync::Location);
|
||||||
SetProtoMethod(
|
SetProtoMethod(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
#include "base_object.h"
|
#include "base_object.h"
|
||||||
|
#include "lru_cache-inl.h"
|
||||||
#include "node_mem.h"
|
#include "node_mem.h"
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
@ -75,9 +77,38 @@ class DatabaseOpenConfiguration {
|
||||||
bool allow_unknown_named_params_ = false;
|
bool allow_unknown_named_params_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DatabaseSync;
|
||||||
|
class StatementSyncIterator;
|
||||||
class StatementSync;
|
class StatementSync;
|
||||||
class BackupJob;
|
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 {
|
class DatabaseSync : public BaseObject {
|
||||||
public:
|
public:
|
||||||
DatabaseSync(Environment* env,
|
DatabaseSync(Environment* env,
|
||||||
|
|
@ -95,6 +126,7 @@ class DatabaseSync : public BaseObject {
|
||||||
static void Dispose(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Dispose(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void Prepare(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 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 Location(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void CustomFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void CustomFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void AggregateFunction(
|
static void AggregateFunction(
|
||||||
|
|
@ -146,6 +178,8 @@ class DatabaseSync : public BaseObject {
|
||||||
std::unordered_set<StatementSync*> statements_;
|
std::unordered_set<StatementSync*> statements_;
|
||||||
|
|
||||||
friend class Session;
|
friend class Session;
|
||||||
|
friend class SQLTagStore;
|
||||||
|
friend class StatementExecutionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StatementSync : public BaseObject {
|
class StatementSync : public BaseObject {
|
||||||
|
|
@ -195,6 +229,8 @@ class StatementSync : public BaseObject {
|
||||||
bool BindValue(const v8::Local<v8::Value>& value, const int index);
|
bool BindValue(const v8::Local<v8::Value>& value, const int index);
|
||||||
|
|
||||||
friend class StatementSyncIterator;
|
friend class StatementSyncIterator;
|
||||||
|
friend class SQLTagStore;
|
||||||
|
friend class StatementExecutionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StatementSyncIterator : public BaseObject {
|
class StatementSyncIterator : public BaseObject {
|
||||||
|
|
@ -248,6 +284,39 @@ class Session : public BaseObject {
|
||||||
BaseObjectWeakPtr<DatabaseSync> database_; // The Parent Database
|
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 {
|
class UserDefinedFunction {
|
||||||
public:
|
public:
|
||||||
UserDefinedFunction(Environment* env,
|
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',
|
'EncapsulatedBits': 'webcrypto.html#class-encapsulatedbits',
|
||||||
'EncapsulatedKey': 'webcrypto.html#class-encapsulatedkey',
|
'EncapsulatedKey': 'webcrypto.html#class-encapsulatedkey',
|
||||||
'SubtleCrypto': 'webcrypto.html#class-subtlecrypto',
|
'SubtleCrypto': 'webcrypto.html#class-subtlecrypto',
|
||||||
|
'Template Literal':
|
||||||
|
`${jsDocPrefix}Reference/Template_literals`,
|
||||||
'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams',
|
'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams',
|
||||||
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
|
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
|
||||||
'AesCbcParams': 'webcrypto.html#class-aescbcparams',
|
'AesCbcParams': 'webcrypto.html#class-aescbcparams',
|
||||||
|
|
@ -234,6 +236,7 @@ const customTypesMap = {
|
||||||
|
|
||||||
'Session': 'sqlite.html#class-session',
|
'Session': 'sqlite.html#class-session',
|
||||||
'StatementSync': 'sqlite.html#class-statementsync',
|
'StatementSync': 'sqlite.html#class-statementsync',
|
||||||
|
'SQLTagStore': 'sqlite.html#class-sqltagstore',
|
||||||
|
|
||||||
'Stream': 'stream.html#stream',
|
'Stream': 'stream.html#stream',
|
||||||
'stream.Duplex': 'stream.html#class-streamduplex',
|
'stream.Duplex': 'stream.html#class-streamduplex',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user