-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
sqlite: add tagged template #58748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
sqlite: add tagged template #58748
Changes from all commits
f3571b7
fd91092
64907f3
d89e17c
c96048a
7526093
c1a858b
01ae21c
ce687dd
a1cbe97
edb9271
e3554c1
e2fb72c
3305de6
172a839
53b2df5
7a7aff1
2b75182
c459c55
4ef4582
3c78a83
686a09c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -347,6 +347,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 | ||
|
@@ -490,6 +537,117 @@ 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. This class cannot be instantiated via its constructor. | ||
Instead, instances are created via the `database.createSQLTagStore()` method. | ||
All APIs exposed by this class execute synchronously. | ||
|
||
The store saves the prepared statement in respect to the SQL query provided, and | ||
returns the same prepared statement when it sees it again. It has a `maxSize` | ||
which defaults to 1000 but can also be passed in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's worth mentioning that all the methods do the automatic parameter binding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see what you mean. I figured 'prepared statements' as it implied we are doing automatic parameter, but I don't mind making it more explicit. I'll change it! |
||
`database.createSQLTagStore(100)`. | ||
|
||
### `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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
Uh oh!
There was an error while loading. Please reload this page.