-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ext/node): implement
node:sqlite
(#27308)
Depends on: - denoland/deno_core#994 - denoland/deno_core#993 - denoland/deno_core#999 - denoland/deno_core#1000 - denoland/deno_core#1001 Closes #24828  --------- Signed-off-by: Divy Srivastava <[email protected]>
- Loading branch information
1 parent
5c64146
commit aeac5a6
Showing
14 changed files
with
689 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
// deno-lint-ignore-file no-console | ||
|
||
import { DatabaseSync } from "node:sqlite"; | ||
import fs from "node:fs"; | ||
|
||
function bench(name, fun, count = 10000) { | ||
const start = Date.now(); | ||
for (let i = 0; i < count; i++) fun(); | ||
const elapsed = Date.now() - start; | ||
const rate = Math.floor(count / (elapsed / 1000)); | ||
console.log(` ${name}: time ${elapsed} ms rate ${rate}`); | ||
} | ||
|
||
for (const name of [":memory:", "test.db"]) { | ||
console.log(`Benchmarking ${name}`); | ||
try { | ||
fs.unlinkSync(name); | ||
} catch { | ||
// Ignore | ||
} | ||
|
||
const db = new DatabaseSync(name); | ||
db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); | ||
|
||
bench("prepare", () => db.prepare("SELECT * FROM test")); | ||
bench("exec", () => db.exec("INSERT INTO test (name) VALUES ('foo')")); | ||
|
||
const stmt = db.prepare("SELECT * FROM test"); | ||
bench("get", () => stmt.get()); | ||
|
||
const stmt2 = db.prepare("SELECT * FROM test WHERE id = ?"); | ||
bench("get (integer bind)", () => stmt2.get(1)); | ||
|
||
bench("all", () => stmt.all(), 1000); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
|
||
use std::cell::Cell; | ||
use std::cell::RefCell; | ||
use std::rc::Rc; | ||
|
||
use deno_core::op2; | ||
use deno_core::GarbageCollected; | ||
use serde::Deserialize; | ||
|
||
use super::SqliteError; | ||
use super::StatementSync; | ||
|
||
#[derive(Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct DatabaseSyncOptions { | ||
#[serde(default = "true_fn")] | ||
open: bool, | ||
#[serde(default = "true_fn")] | ||
enable_foreign_key_constraints: bool, | ||
} | ||
|
||
fn true_fn() -> bool { | ||
true | ||
} | ||
|
||
impl Default for DatabaseSyncOptions { | ||
fn default() -> Self { | ||
DatabaseSyncOptions { | ||
open: true, | ||
enable_foreign_key_constraints: true, | ||
} | ||
} | ||
} | ||
|
||
pub struct DatabaseSync { | ||
conn: Rc<RefCell<Option<rusqlite::Connection>>>, | ||
options: DatabaseSyncOptions, | ||
location: String, | ||
} | ||
|
||
impl GarbageCollected for DatabaseSync {} | ||
|
||
// Represents a single connection to a SQLite database. | ||
#[op2] | ||
impl DatabaseSync { | ||
// Constructs a new `DatabaseSync` instance. | ||
// | ||
// A SQLite database can be stored in a file or in memory. To | ||
// use a file-backed database, the `location` should be a path. | ||
// To use an in-memory database, the `location` should be special | ||
// name ":memory:". | ||
#[constructor] | ||
#[cppgc] | ||
fn new( | ||
#[string] location: String, | ||
#[serde] options: Option<DatabaseSyncOptions>, | ||
) -> Result<DatabaseSync, SqliteError> { | ||
let options = options.unwrap_or_default(); | ||
|
||
let db = if options.open { | ||
let db = rusqlite::Connection::open(&location)?; | ||
if options.enable_foreign_key_constraints { | ||
db.execute("PRAGMA foreign_keys = ON", [])?; | ||
} | ||
Some(db) | ||
} else { | ||
None | ||
}; | ||
|
||
Ok(DatabaseSync { | ||
conn: Rc::new(RefCell::new(db)), | ||
location, | ||
options, | ||
}) | ||
} | ||
|
||
// Opens the database specified by `location` of this instance. | ||
// | ||
// This method should only be used when the database is not opened | ||
// via the constructor. An exception is thrown if the database is | ||
// already opened. | ||
#[fast] | ||
fn open(&self) -> Result<(), SqliteError> { | ||
if self.conn.borrow().is_some() { | ||
return Err(SqliteError::AlreadyOpen); | ||
} | ||
|
||
let db = rusqlite::Connection::open(&self.location)?; | ||
if self.options.enable_foreign_key_constraints { | ||
db.execute("PRAGMA foreign_keys = ON", [])?; | ||
} | ||
|
||
*self.conn.borrow_mut() = Some(db); | ||
|
||
Ok(()) | ||
} | ||
|
||
// Closes the database connection. An exception is thrown if the | ||
// database is not open. | ||
#[fast] | ||
fn close(&self) -> Result<(), SqliteError> { | ||
if self.conn.borrow().is_none() { | ||
return Err(SqliteError::AlreadyClosed); | ||
} | ||
|
||
*self.conn.borrow_mut() = None; | ||
Ok(()) | ||
} | ||
|
||
// This method allows one or more SQL statements to be executed | ||
// without returning any results. | ||
// | ||
// This method is a wrapper around sqlite3_exec(). | ||
#[fast] | ||
fn exec(&self, #[string] sql: &str) -> Result<(), SqliteError> { | ||
let db = self.conn.borrow(); | ||
let db = db.as_ref().ok_or(SqliteError::InUse)?; | ||
|
||
let mut stmt = db.prepare_cached(sql)?; | ||
stmt.raw_execute()?; | ||
|
||
Ok(()) | ||
} | ||
|
||
// Compiles an SQL statement into a prepared statement. | ||
// | ||
// This method is a wrapper around `sqlite3_prepare_v2()`. | ||
#[cppgc] | ||
fn prepare(&self, #[string] sql: &str) -> Result<StatementSync, SqliteError> { | ||
let db = self.conn.borrow(); | ||
let db = db.as_ref().ok_or(SqliteError::InUse)?; | ||
|
||
// SAFETY: lifetime of the connection is guaranteed by reference | ||
// counting. | ||
let raw_handle = unsafe { db.handle() }; | ||
|
||
let mut raw_stmt = std::ptr::null_mut(); | ||
|
||
// SAFETY: `sql` points to a valid memory location and its length | ||
// is correct. | ||
let r = unsafe { | ||
libsqlite3_sys::sqlite3_prepare_v2( | ||
raw_handle, | ||
sql.as_ptr() as *const _, | ||
sql.len() as i32, | ||
&mut raw_stmt, | ||
std::ptr::null_mut(), | ||
) | ||
}; | ||
|
||
if r != libsqlite3_sys::SQLITE_OK { | ||
return Err(SqliteError::PrepareFailed); | ||
} | ||
|
||
Ok(StatementSync { | ||
inner: raw_stmt, | ||
db: self.conn.clone(), | ||
use_big_ints: Cell::new(false), | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
|
||
mod database; | ||
mod statement; | ||
|
||
pub use database::DatabaseSync; | ||
pub use statement::StatementSync; | ||
|
||
#[derive(Debug, thiserror::Error, deno_error::JsError)] | ||
pub enum SqliteError { | ||
#[class(generic)] | ||
#[error(transparent)] | ||
SqliteError(#[from] rusqlite::Error), | ||
#[class(generic)] | ||
#[error("Database is already in use")] | ||
InUse, | ||
#[class(generic)] | ||
#[error("Failed to step statement")] | ||
FailedStep, | ||
#[class(generic)] | ||
#[error("Failed to bind parameter. {0}")] | ||
FailedBind(&'static str), | ||
#[class(generic)] | ||
#[error("Unknown column type")] | ||
UnknownColumnType, | ||
#[class(generic)] | ||
#[error("Failed to get SQL")] | ||
GetSqlFailed, | ||
#[class(generic)] | ||
#[error("Database is already closed")] | ||
AlreadyClosed, | ||
#[class(generic)] | ||
#[error("Database is already open")] | ||
AlreadyOpen, | ||
#[class(generic)] | ||
#[error("Failed to prepare statement")] | ||
PrepareFailed, | ||
#[class(generic)] | ||
#[error("Invalid constructor")] | ||
InvalidConstructor, | ||
} |
Oops, something went wrong.