mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 04:52:26 -05:00
Compare commits
27 commits
998f5cb5a4
...
3b441795b3
Author | SHA1 | Date | |
---|---|---|---|
|
3b441795b3 | ||
|
fa2a41ef27 | ||
|
8b030f3247 | ||
|
f9654b35d6 | ||
|
83c6bfde85 | ||
|
58f348b496 | ||
|
0c0a3e6505 | ||
|
a43ea7ae19 | ||
|
636a6c3265 | ||
|
f0409a6122 | ||
|
574892aa02 | ||
|
e21982d159 | ||
|
a9f85a70e2 | ||
|
d104b0b8b3 | ||
|
9e37da438c | ||
|
9e7306ac7f | ||
|
008738058f | ||
|
95763d3f43 | ||
|
57c29c326e | ||
|
11c4d61b86 | ||
|
509bd954da | ||
|
bf6825e9fa | ||
|
3279935647 | ||
|
87574d241e | ||
|
72c96bf7d3 | ||
|
87de65525a | ||
|
0ab878cda8 |
14 changed files with 681 additions and 6 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -720,13 +720,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.106"
|
version = "1.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2"
|
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2065,6 +2065,7 @@ dependencies = [
|
||||||
"k256",
|
"k256",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"libc",
|
"libc",
|
||||||
|
"libsqlite3-sys",
|
||||||
"libz-sys",
|
"libz-sys",
|
||||||
"md-5",
|
"md-5",
|
||||||
"md4",
|
"md4",
|
||||||
|
@ -2086,6 +2087,7 @@ dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"ripemd",
|
"ripemd",
|
||||||
"rsa",
|
"rsa",
|
||||||
|
"rusqlite",
|
||||||
"scrypt",
|
"scrypt",
|
||||||
"sec1",
|
"sec1",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -4841,9 +4843,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b694a822684ddb75df4d657029161431bcb4a85c1856952f845b76912bc6fec"
|
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
|
36
cli/bench/sqlite.js
Normal file
36
cli/bench/sqlite.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. 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);
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ ipnetwork = "0.20.0"
|
||||||
k256 = "0.13.1"
|
k256 = "0.13.1"
|
||||||
lazy-regex.workspace = true
|
lazy-regex.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
|
libsqlite3-sys = "0.30.1"
|
||||||
libz-sys.workspace = true
|
libz-sys.workspace = true
|
||||||
md-5 = { version = "0.10.5", features = ["oid"] }
|
md-5 = { version = "0.10.5", features = ["oid"] }
|
||||||
md4 = "0.10.2"
|
md4 = "0.10.2"
|
||||||
|
@ -80,6 +81,7 @@ regex.workspace = true
|
||||||
ring.workspace = true
|
ring.workspace = true
|
||||||
ripemd = { version = "0.1.3", features = ["oid"] }
|
ripemd = { version = "0.1.3", features = ["oid"] }
|
||||||
rsa.workspace = true
|
rsa.workspace = true
|
||||||
|
rusqlite.workspace = true
|
||||||
scrypt = "0.11.0"
|
scrypt = "0.11.0"
|
||||||
sec1.workspace = true
|
sec1.workspace = true
|
||||||
serde = "1.0.149"
|
serde = "1.0.149"
|
||||||
|
|
|
@ -438,7 +438,9 @@ deno_core::extension!(deno_node,
|
||||||
ops::inspector::op_inspector_enabled,
|
ops::inspector::op_inspector_enabled,
|
||||||
],
|
],
|
||||||
objects = [
|
objects = [
|
||||||
ops::perf_hooks::EldHistogram
|
ops::perf_hooks::EldHistogram,
|
||||||
|
ops::sqlite::DatabaseSync,
|
||||||
|
ops::sqlite::StatementSync
|
||||||
],
|
],
|
||||||
esm_entry_point = "ext:deno_node/02_init.js",
|
esm_entry_point = "ext:deno_node/02_init.js",
|
||||||
esm = [
|
esm = [
|
||||||
|
@ -662,6 +664,7 @@ deno_core::extension!(deno_node,
|
||||||
"node:readline" = "readline.ts",
|
"node:readline" = "readline.ts",
|
||||||
"node:readline/promises" = "readline/promises.ts",
|
"node:readline/promises" = "readline/promises.ts",
|
||||||
"node:repl" = "repl.ts",
|
"node:repl" = "repl.ts",
|
||||||
|
"node:sqlite" = "sqlite.ts",
|
||||||
"node:stream" = "stream.ts",
|
"node:stream" = "stream.ts",
|
||||||
"node:stream/consumers" = "stream/consumers.mjs",
|
"node:stream/consumers" = "stream/consumers.mjs",
|
||||||
"node:stream/promises" = "stream/promises.mjs",
|
"node:stream/promises" = "stream/promises.mjs",
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub mod os;
|
||||||
pub mod perf_hooks;
|
pub mod perf_hooks;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod require;
|
pub mod require;
|
||||||
|
pub mod sqlite;
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod v8;
|
pub mod v8;
|
||||||
|
|
162
ext/node/ops/sqlite/database.rs
Normal file
162
ext/node/ops/sqlite/database.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. 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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
33
ext/node/ops/sqlite/mod.rs
Normal file
33
ext/node/ops/sqlite/mod.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
mod database;
|
||||||
|
mod statement;
|
||||||
|
|
||||||
|
pub use database::DatabaseSync;
|
||||||
|
pub use statement::StatementSync;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum SqliteError {
|
||||||
|
#[error(transparent)]
|
||||||
|
SqliteError(#[from] rusqlite::Error),
|
||||||
|
#[error("Database is already in use")]
|
||||||
|
InUse,
|
||||||
|
#[error("Failed to step statement")]
|
||||||
|
FailedStep,
|
||||||
|
#[error("Failed to bind parameter. {0}")]
|
||||||
|
FailedBind(&'static str),
|
||||||
|
#[error("Unknown column type")]
|
||||||
|
UnknownColumnType,
|
||||||
|
#[error("Failed to get SQL")]
|
||||||
|
GetSqlFailed,
|
||||||
|
#[error("Database is already closed")]
|
||||||
|
AlreadyClosed,
|
||||||
|
#[error("Database is already open")]
|
||||||
|
AlreadyOpen,
|
||||||
|
#[error("Failed to prepare statement")]
|
||||||
|
PrepareFailed,
|
||||||
|
#[error("Invalid constructor")]
|
||||||
|
InvalidConstructor,
|
||||||
|
#[error(transparent)]
|
||||||
|
Other(deno_core::error::AnyError),
|
||||||
|
}
|
357
ext/node/ops/sqlite/statement.rs
Normal file
357
ext/node/ops/sqlite/statement.rs
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use deno_core::op2;
|
||||||
|
use deno_core::v8;
|
||||||
|
use deno_core::GarbageCollected;
|
||||||
|
use libsqlite3_sys as ffi;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::SqliteError;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RunStatementResult {
|
||||||
|
last_insert_rowid: i64,
|
||||||
|
changes: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StatementSync {
|
||||||
|
pub inner: *mut ffi::sqlite3_stmt,
|
||||||
|
pub db: Rc<RefCell<Option<rusqlite::Connection>>>,
|
||||||
|
|
||||||
|
pub use_big_ints: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for StatementSync {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// no other references to this pointer exist.
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_finalize(self.inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ColumnIterator<'a> {
|
||||||
|
stmt: &'a StatementSync,
|
||||||
|
index: i32,
|
||||||
|
count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ColumnIterator<'a> {
|
||||||
|
fn new(stmt: &'a StatementSync) -> Self {
|
||||||
|
let count = stmt.column_count();
|
||||||
|
ColumnIterator {
|
||||||
|
stmt,
|
||||||
|
index: 0,
|
||||||
|
count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_count(&self) -> usize {
|
||||||
|
self.count as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ColumnIterator<'a> {
|
||||||
|
type Item = (i32, &'a [u8]);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.index >= self.count {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = self.index;
|
||||||
|
let name = self.stmt.column_name(self.index);
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
|
Some((index, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GarbageCollected for StatementSync {}
|
||||||
|
|
||||||
|
impl StatementSync {
|
||||||
|
// Clear the prepared statement back to its initial state.
|
||||||
|
fn reset(&self) {
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_reset(self.inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the prepared statement.
|
||||||
|
fn step(&self) -> Result<bool, SqliteError> {
|
||||||
|
let raw = self.inner;
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe {
|
||||||
|
let r = ffi::sqlite3_step(raw);
|
||||||
|
if r == ffi::SQLITE_DONE {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if r != ffi::SQLITE_ROW {
|
||||||
|
return Err(SqliteError::FailedStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_count(&self) -> i32 {
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe { ffi::sqlite3_column_count(self.inner) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_name(&self, index: i32) -> &[u8] {
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe {
|
||||||
|
let name = ffi::sqlite3_column_name(self.inner, index);
|
||||||
|
std::ffi::CStr::from_ptr(name as _).to_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_value<'a>(
|
||||||
|
&self,
|
||||||
|
index: i32,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
) -> v8::Local<'a, v8::Value> {
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe {
|
||||||
|
match ffi::sqlite3_column_type(self.inner, index) {
|
||||||
|
ffi::SQLITE_INTEGER => {
|
||||||
|
let value = ffi::sqlite3_column_int64(self.inner, index);
|
||||||
|
if self.use_big_ints.get() {
|
||||||
|
v8::BigInt::new_from_i64(scope, value).into()
|
||||||
|
} else {
|
||||||
|
v8::Integer::new(scope, value as _).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi::SQLITE_FLOAT => {
|
||||||
|
let value = ffi::sqlite3_column_double(self.inner, index);
|
||||||
|
v8::Number::new(scope, value).into()
|
||||||
|
}
|
||||||
|
ffi::SQLITE_TEXT => {
|
||||||
|
let value = ffi::sqlite3_column_text(self.inner, index);
|
||||||
|
let value = std::ffi::CStr::from_ptr(value as _);
|
||||||
|
v8::String::new_from_utf8(
|
||||||
|
scope,
|
||||||
|
value.to_bytes(),
|
||||||
|
v8::NewStringType::Normal,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
ffi::SQLITE_BLOB => {
|
||||||
|
let value = ffi::sqlite3_column_blob(self.inner, index);
|
||||||
|
let size = ffi::sqlite3_column_bytes(self.inner, index);
|
||||||
|
let value =
|
||||||
|
std::slice::from_raw_parts(value as *const u8, size as usize);
|
||||||
|
let value =
|
||||||
|
v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec())
|
||||||
|
.make_shared();
|
||||||
|
v8::ArrayBuffer::with_backing_store(scope, &value).into()
|
||||||
|
}
|
||||||
|
ffi::SQLITE_NULL => v8::null(scope).into(),
|
||||||
|
_ => v8::undefined(scope).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the current row of the prepared statement.
|
||||||
|
fn read_row<'a>(
|
||||||
|
&self,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
) -> Result<Option<v8::Local<'a, v8::Object>>, SqliteError> {
|
||||||
|
if self.step()? {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let iter = ColumnIterator::new(self);
|
||||||
|
|
||||||
|
let num_cols = iter.column_count();
|
||||||
|
|
||||||
|
let mut names = Vec::with_capacity(num_cols);
|
||||||
|
let mut values = Vec::with_capacity(num_cols);
|
||||||
|
|
||||||
|
for (index, name) in iter {
|
||||||
|
let value = self.column_value(index, scope);
|
||||||
|
let name =
|
||||||
|
v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal)
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
|
||||||
|
names.push(name);
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let null = v8::null(scope).into();
|
||||||
|
let result =
|
||||||
|
v8::Object::with_prototype_and_properties(scope, null, &names, &values);
|
||||||
|
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the parameters to the prepared statement.
|
||||||
|
fn bind_params(
|
||||||
|
&self,
|
||||||
|
scope: &mut v8::HandleScope,
|
||||||
|
params: Option<&v8::FunctionCallbackArguments>,
|
||||||
|
) -> Result<(), SqliteError> {
|
||||||
|
let raw = self.inner;
|
||||||
|
|
||||||
|
if let Some(params) = params {
|
||||||
|
let len = params.length();
|
||||||
|
for i in 0..len {
|
||||||
|
let value = params.get(i);
|
||||||
|
|
||||||
|
if value.is_number() {
|
||||||
|
let value = value.number_value(scope).unwrap();
|
||||||
|
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_double(raw, i + 1, value);
|
||||||
|
}
|
||||||
|
} else if value.is_string() {
|
||||||
|
let value = value.to_rust_string_lossy(scope);
|
||||||
|
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
//
|
||||||
|
// SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data.
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_text(
|
||||||
|
raw,
|
||||||
|
i + 1,
|
||||||
|
value.as_ptr() as *const _,
|
||||||
|
value.len() as i32,
|
||||||
|
ffi::SQLITE_TRANSIENT(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if value.is_null() {
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_null(raw, i + 1);
|
||||||
|
}
|
||||||
|
} else if value.is_array_buffer_view() {
|
||||||
|
let value: v8::Local<v8::ArrayBufferView> = value.try_into().unwrap();
|
||||||
|
let data = value.data();
|
||||||
|
let size = value.byte_length();
|
||||||
|
|
||||||
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
|
||||||
|
// as it lives as long as the StatementSync instance.
|
||||||
|
//
|
||||||
|
// SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data.
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_blob(
|
||||||
|
raw,
|
||||||
|
i + 1,
|
||||||
|
data,
|
||||||
|
size as i32,
|
||||||
|
ffi::SQLITE_TRANSIENT(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(SqliteError::FailedBind("Unsupported type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents a single prepared statement. Cannot be initialized directly via constructor.
|
||||||
|
// Instances are created using `DatabaseSync#prepare`.
|
||||||
|
//
|
||||||
|
// A prepared statement is an efficient binary representation of the SQL used to create it.
|
||||||
|
#[op2]
|
||||||
|
impl StatementSync {
|
||||||
|
#[constructor]
|
||||||
|
#[cppgc]
|
||||||
|
fn new(_: bool) -> Result<StatementSync, SqliteError> {
|
||||||
|
Err(SqliteError::InvalidConstructor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes a prepared statement and returns the first result as an object.
|
||||||
|
//
|
||||||
|
// The prepared statement does not return any results, this method returns undefined.
|
||||||
|
// Optionally, parameters can be bound to the prepared statement.
|
||||||
|
fn get<'a>(
|
||||||
|
&self,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
||||||
|
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
|
||||||
|
self.reset();
|
||||||
|
|
||||||
|
self.bind_params(scope, params)?;
|
||||||
|
|
||||||
|
let entry = self.read_row(scope)?;
|
||||||
|
let result = entry
|
||||||
|
.map(|r| r.into())
|
||||||
|
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes a prepared statement and returns an object summarizing the resulting
|
||||||
|
// changes.
|
||||||
|
//
|
||||||
|
// Optionally, parameters can be bound to the prepared statement.
|
||||||
|
#[serde]
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
scope: &mut v8::HandleScope,
|
||||||
|
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
||||||
|
) -> Result<RunStatementResult, SqliteError> {
|
||||||
|
let db = self.db.borrow();
|
||||||
|
let db = db.as_ref().ok_or(SqliteError::InUse)?;
|
||||||
|
|
||||||
|
self.bind_params(scope, params)?;
|
||||||
|
self.step()?;
|
||||||
|
|
||||||
|
self.reset();
|
||||||
|
|
||||||
|
Ok(RunStatementResult {
|
||||||
|
last_insert_rowid: db.last_insert_rowid(),
|
||||||
|
changes: db.changes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes a prepared statement and returns all results as an array of objects.
|
||||||
|
//
|
||||||
|
// If the prepared statement does not return any results, this method returns an empty array.
|
||||||
|
// Optionally, parameters can be bound to the prepared statement.
|
||||||
|
fn all<'a>(
|
||||||
|
&self,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
||||||
|
) -> Result<v8::Local<'a, v8::Array>, SqliteError> {
|
||||||
|
let mut arr = vec![];
|
||||||
|
|
||||||
|
self.bind_params(scope, params)?;
|
||||||
|
while let Some(result) = self.read_row(scope)? {
|
||||||
|
arr.push(result.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reset();
|
||||||
|
|
||||||
|
let arr = v8::Array::new_with_elements(scope, &arr);
|
||||||
|
Ok(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fast]
|
||||||
|
fn set_read_big_ints(&self, enabled: bool) {
|
||||||
|
self.use_big_ints.set(enabled);
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,7 @@ generate_builtin_node_module_lists! {
|
||||||
"readline",
|
"readline",
|
||||||
"readline/promises",
|
"readline/promises",
|
||||||
"repl",
|
"repl",
|
||||||
|
"sqlite",
|
||||||
"stream",
|
"stream",
|
||||||
"stream/consumers",
|
"stream/consumers",
|
||||||
"stream/promises",
|
"stream/promises",
|
||||||
|
|
|
@ -142,6 +142,7 @@ import querystring from "node:querystring";
|
||||||
import readline from "node:readline";
|
import readline from "node:readline";
|
||||||
import readlinePromises from "node:readline/promises";
|
import readlinePromises from "node:readline/promises";
|
||||||
import repl from "node:repl";
|
import repl from "node:repl";
|
||||||
|
import sqlite from "node:sqlite";
|
||||||
import stream from "node:stream";
|
import stream from "node:stream";
|
||||||
import streamConsumers from "node:stream/consumers";
|
import streamConsumers from "node:stream/consumers";
|
||||||
import streamPromises from "node:stream/promises";
|
import streamPromises from "node:stream/promises";
|
||||||
|
@ -253,6 +254,7 @@ function setupBuiltinModules() {
|
||||||
readline,
|
readline,
|
||||||
"readline/promises": readlinePromises,
|
"readline/promises": readlinePromises,
|
||||||
repl,
|
repl,
|
||||||
|
sqlite,
|
||||||
stream,
|
stream,
|
||||||
"stream/consumers": streamConsumers,
|
"stream/consumers": streamConsumers,
|
||||||
"stream/promises": streamPromises,
|
"stream/promises": streamPromises,
|
||||||
|
|
9
ext/node/polyfills/sqlite.ts
Normal file
9
ext/node/polyfills/sqlite.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { DatabaseSync } from "ext:core/ops";
|
||||||
|
|
||||||
|
export { DatabaseSync };
|
||||||
|
|
||||||
|
export default {
|
||||||
|
DatabaseSync,
|
||||||
|
};
|
|
@ -8725,6 +8725,7 @@ fn lsp_completions_node_specifier() {
|
||||||
"node:readline",
|
"node:readline",
|
||||||
"node:readline/promises",
|
"node:readline/promises",
|
||||||
"node:repl",
|
"node:repl",
|
||||||
|
"node:sqlite",
|
||||||
"node:stream",
|
"node:stream",
|
||||||
"node:stream/consumers",
|
"node:stream/consumers",
|
||||||
"node:stream/promises",
|
"node:stream/promises",
|
||||||
|
|
|
@ -90,6 +90,7 @@ util::unit_test_factory!(
|
||||||
querystring_test,
|
querystring_test,
|
||||||
readline_test,
|
readline_test,
|
||||||
repl_test,
|
repl_test,
|
||||||
|
sqlite_test,
|
||||||
stream_test,
|
stream_test,
|
||||||
string_decoder_test,
|
string_decoder_test,
|
||||||
timers_test,
|
timers_test,
|
||||||
|
|
65
tests/unit_node/sqlite_test.ts
Normal file
65
tests/unit_node/sqlite_test.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { DatabaseSync } from "node:sqlite";
|
||||||
|
import { assertEquals, assertThrows } from "@std/assert";
|
||||||
|
|
||||||
|
Deno.test("[node/sqlite] in-memory databases", () => {
|
||||||
|
const db1 = new DatabaseSync(":memory:");
|
||||||
|
const db2 = new DatabaseSync(":memory:");
|
||||||
|
db1.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);");
|
||||||
|
db1.exec("INSERT INTO data (key) VALUES (1);");
|
||||||
|
|
||||||
|
db2.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);");
|
||||||
|
db2.exec("INSERT INTO data (key) VALUES (1);");
|
||||||
|
|
||||||
|
assertEquals(db1.prepare("SELECT * FROM data").all(), [{
|
||||||
|
key: 1,
|
||||||
|
__proto__: null,
|
||||||
|
}]);
|
||||||
|
assertEquals(db2.prepare("SELECT * FROM data").all(), [{
|
||||||
|
key: 1,
|
||||||
|
__proto__: null,
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/sqlite] Errors originating from SQLite should be thrown", () => {
|
||||||
|
const db = new DatabaseSync(":memory:");
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE test(
|
||||||
|
key INTEGER PRIMARY KEY
|
||||||
|
) STRICT;
|
||||||
|
`);
|
||||||
|
const stmt = db.prepare("INSERT INTO test(key) VALUES(?)");
|
||||||
|
assertEquals(stmt.run(1), { lastInsertRowid: 1, changes: 1 });
|
||||||
|
|
||||||
|
assertThrows(() => stmt.run(1), Error);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{
|
||||||
|
permissions: { read: true, write: true },
|
||||||
|
name: "[node/sqlite] PRAGMAs are supported",
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const tempDir = Deno.makeTempDirSync();
|
||||||
|
const db = new DatabaseSync(`${tempDir}/test.db`);
|
||||||
|
|
||||||
|
assertEquals(db.prepare("PRAGMA journal_mode = WAL").get(), {
|
||||||
|
journal_mode: "wal",
|
||||||
|
__proto__: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test("[node/sqlite] StatementSync read bigints are supported", () => {
|
||||||
|
const db = new DatabaseSync(":memory:");
|
||||||
|
db.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);");
|
||||||
|
db.exec("INSERT INTO data (key) VALUES (1);");
|
||||||
|
|
||||||
|
const stmt = db.prepare("SELECT * FROM data");
|
||||||
|
assertEquals(stmt.get(), { key: 1, __proto__: null });
|
||||||
|
|
||||||
|
stmt.setReadBigInts(true);
|
||||||
|
assertEquals(stmt.get(), { key: 1n, __proto__: null });
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue