mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 04:52:26 -05:00
bench
This commit is contained in:
parent
3279935647
commit
bf6825e9fa
3 changed files with 212 additions and 151 deletions
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 (e) {
|
||||||
|
// 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);
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ pub enum SqliteError {
|
||||||
AlreadyOpen,
|
AlreadyOpen,
|
||||||
#[error("Failed to prepare statement")]
|
#[error("Failed to prepare statement")]
|
||||||
PrepareFailed,
|
PrepareFailed,
|
||||||
|
#[error("Invalid constructor")]
|
||||||
|
InvalidConstructor,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Other(deno_core::error::AnyError),
|
Other(deno_core::error::AnyError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::rc::Rc;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use deno_core::v8;
|
use deno_core::v8;
|
||||||
use deno_core::GarbageCollected;
|
use deno_core::GarbageCollected;
|
||||||
|
use libsqlite3_sys as ffi;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::SqliteError;
|
use super::SqliteError;
|
||||||
|
@ -18,150 +19,194 @@ pub struct RunStatementResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StatementSync {
|
pub struct StatementSync {
|
||||||
pub inner: *mut libsqlite3_sys::sqlite3_stmt,
|
pub inner: *mut ffi::sqlite3_stmt,
|
||||||
pub db: Rc<RefCell<Option<rusqlite::Connection>>>,
|
pub db: Rc<RefCell<Option<rusqlite::Connection>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GarbageCollected for StatementSync {}
|
impl GarbageCollected for StatementSync {}
|
||||||
|
|
||||||
fn read_entry<'a>(
|
impl StatementSync {
|
||||||
raw: *mut libsqlite3_sys::sqlite3_stmt,
|
// Clear the prepared statement back to its initial state.
|
||||||
scope: &mut v8::HandleScope<'a>,
|
fn reset(&self) {
|
||||||
) -> Result<Option<v8::Local<'a, v8::Object>>, SqliteError> {
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt.
|
||||||
let result = v8::Object::new(scope);
|
unsafe {
|
||||||
unsafe {
|
ffi::sqlite3_reset(self.inner);
|
||||||
let r = libsqlite3_sys::sqlite3_step(raw);
|
|
||||||
|
|
||||||
if r == libsqlite3_sys::SQLITE_DONE {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
if r != libsqlite3_sys::SQLITE_ROW {
|
|
||||||
return Err(SqliteError::FailedStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
let columns = libsqlite3_sys::sqlite3_column_count(raw);
|
|
||||||
|
|
||||||
for i in 0..columns {
|
|
||||||
let name = libsqlite3_sys::sqlite3_column_name(raw, i);
|
|
||||||
let name = std::ffi::CStr::from_ptr(name).to_string_lossy().to_string();
|
|
||||||
let value = match libsqlite3_sys::sqlite3_column_type(raw, i) {
|
|
||||||
libsqlite3_sys::SQLITE_INTEGER => {
|
|
||||||
let value = libsqlite3_sys::sqlite3_column_int64(raw, i);
|
|
||||||
v8::Integer::new(scope, value as _).into()
|
|
||||||
}
|
|
||||||
libsqlite3_sys::SQLITE_FLOAT => {
|
|
||||||
let value = libsqlite3_sys::sqlite3_column_double(raw, i);
|
|
||||||
v8::Number::new(scope, value).into()
|
|
||||||
}
|
|
||||||
libsqlite3_sys::SQLITE_TEXT => {
|
|
||||||
let value = libsqlite3_sys::sqlite3_column_text(raw, i);
|
|
||||||
let value = std::ffi::CStr::from_ptr(value as _)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
v8::String::new_from_utf8(
|
|
||||||
scope,
|
|
||||||
value.as_bytes(),
|
|
||||||
v8::NewStringType::Normal,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
libsqlite3_sys::SQLITE_BLOB => {
|
|
||||||
let value = libsqlite3_sys::sqlite3_column_blob(raw, i);
|
|
||||||
let size = libsqlite3_sys::sqlite3_column_bytes(raw, i);
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(),
|
|
||||||
_ => {
|
|
||||||
return Err(SqliteError::UnknownColumnType);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = v8::String::new_from_utf8(
|
|
||||||
scope,
|
|
||||||
name.as_bytes(),
|
|
||||||
v8::NewStringType::Normal,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.into();
|
|
||||||
result.set(scope, name, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(result))
|
// Evaluate the prepared statement.
|
||||||
}
|
fn step(&self) -> Result<bool, SqliteError> {
|
||||||
|
let raw = self.inner;
|
||||||
fn bind_params(
|
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt.
|
||||||
scope: &mut v8::HandleScope,
|
unsafe {
|
||||||
raw: *mut libsqlite3_sys::sqlite3_stmt,
|
let r = ffi::sqlite3_step(raw);
|
||||||
params: Option<&v8::FunctionCallbackArguments>,
|
if r == ffi::SQLITE_DONE {
|
||||||
) -> Result<(), SqliteError> {
|
return Ok(true);
|
||||||
if let Some(params) = params {
|
}
|
||||||
let len = params.length();
|
if r != ffi::SQLITE_ROW {
|
||||||
for i in 0..len {
|
return Err(SqliteError::FailedStep);
|
||||||
let value = params.get(i);
|
|
||||||
|
|
||||||
if value.is_number() {
|
|
||||||
let value = value.number_value(scope).unwrap();
|
|
||||||
unsafe {
|
|
||||||
libsqlite3_sys::sqlite3_bind_double(raw, i as i32 + 1, value);
|
|
||||||
}
|
|
||||||
} else if value.is_string() {
|
|
||||||
let value = value.to_rust_string_lossy(scope);
|
|
||||||
unsafe {
|
|
||||||
libsqlite3_sys::sqlite3_bind_text(
|
|
||||||
raw,
|
|
||||||
i as i32 + 1,
|
|
||||||
value.as_ptr() as *const i8,
|
|
||||||
value.len() as i32,
|
|
||||||
libsqlite3_sys::SQLITE_TRANSIENT(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if value.is_null() {
|
|
||||||
unsafe {
|
|
||||||
libsqlite3_sys::sqlite3_bind_null(raw, i as i32 + 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(SqliteError::FailedBind("Unsupported type"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
// 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> {
|
||||||
|
let result = v8::Object::new(scope);
|
||||||
|
let raw = self.inner;
|
||||||
|
unsafe {
|
||||||
|
if self.step()? {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns = ffi::sqlite3_column_count(raw);
|
||||||
|
|
||||||
|
for i in 0..columns {
|
||||||
|
let name = ffi::sqlite3_column_name(raw, i);
|
||||||
|
let name = std::ffi::CStr::from_ptr(name).to_string_lossy();
|
||||||
|
let value = match ffi::sqlite3_column_type(raw, i) {
|
||||||
|
ffi::SQLITE_INTEGER => {
|
||||||
|
let value = ffi::sqlite3_column_int64(raw, i);
|
||||||
|
v8::Integer::new(scope, value as _).into()
|
||||||
|
}
|
||||||
|
ffi::SQLITE_FLOAT => {
|
||||||
|
let value = ffi::sqlite3_column_double(raw, i);
|
||||||
|
v8::Number::new(scope, value).into()
|
||||||
|
}
|
||||||
|
ffi::SQLITE_TEXT => {
|
||||||
|
let value = ffi::sqlite3_column_text(raw, i);
|
||||||
|
let value = std::ffi::CStr::from_ptr(value as _)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
v8::String::new_from_utf8(
|
||||||
|
scope,
|
||||||
|
value.as_bytes(),
|
||||||
|
v8::NewStringType::Normal,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
ffi::SQLITE_BLOB => {
|
||||||
|
let value = ffi::sqlite3_column_blob(raw, i);
|
||||||
|
let size = ffi::sqlite3_column_bytes(raw, i);
|
||||||
|
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(),
|
||||||
|
_ => {
|
||||||
|
return Err(SqliteError::UnknownColumnType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = v8::String::new_from_utf8(
|
||||||
|
scope,
|
||||||
|
name.as_bytes(),
|
||||||
|
v8::NewStringType::Normal,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
result.set(scope, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_double(raw, i as i32 + 1, value);
|
||||||
|
}
|
||||||
|
} else if value.is_string() {
|
||||||
|
let value = value.to_rust_string_lossy(scope);
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_text(
|
||||||
|
raw,
|
||||||
|
i as i32 + 1,
|
||||||
|
value.as_ptr() as *const i8,
|
||||||
|
value.len() as i32,
|
||||||
|
ffi::SQLITE_TRANSIENT(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if value.is_null() {
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_null(raw, i as i32 + 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();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_bind_blob(
|
||||||
|
raw,
|
||||||
|
i as i32 + 1,
|
||||||
|
data,
|
||||||
|
size as i32,
|
||||||
|
ffi::SQLITE_TRANSIENT(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(SqliteError::FailedBind("Unsupported type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
impl StatementSync {
|
impl StatementSync {
|
||||||
#[constructor]
|
#[constructor]
|
||||||
#[cppgc]
|
#[cppgc]
|
||||||
fn new(_: bool) -> StatementSync {
|
fn new(_: bool) -> Result<StatementSync, SqliteError> {
|
||||||
unimplemented!()
|
Err(SqliteError::InvalidConstructor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get<'a>(
|
fn get<'a>(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut v8::HandleScope<'a>,
|
scope: &mut v8::HandleScope<'a>,
|
||||||
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
||||||
) -> Result<v8::Local<'a, v8::Object>, SqliteError> {
|
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
|
||||||
let raw = self.inner;
|
self.bind_params(scope, params)?;
|
||||||
|
|
||||||
unsafe {
|
let entry = self.read_row(scope)?;
|
||||||
libsqlite3_sys::sqlite3_reset(raw);
|
let result = entry
|
||||||
}
|
.map(|r| r.into())
|
||||||
|
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||||
|
|
||||||
bind_params(scope, raw, params)?;
|
self.reset();
|
||||||
|
|
||||||
let result = read_entry(raw, scope)
|
|
||||||
.map(|r| r.unwrap_or_else(|| v8::Object::new(scope)))?;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
libsqlite3_sys::sqlite3_reset(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
@ -172,34 +217,17 @@ impl StatementSync {
|
||||||
scope: &mut v8::HandleScope,
|
scope: &mut v8::HandleScope,
|
||||||
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
||||||
) -> Result<RunStatementResult, SqliteError> {
|
) -> Result<RunStatementResult, SqliteError> {
|
||||||
let raw = self.inner;
|
|
||||||
let db = self.db.borrow();
|
let db = self.db.borrow();
|
||||||
let db = db.as_ref().unwrap();
|
let db = db.as_ref().ok_or(SqliteError::InUse)?;
|
||||||
|
|
||||||
let last_insert_rowid;
|
self.bind_params(scope, params)?;
|
||||||
let changes;
|
self.step()?;
|
||||||
|
|
||||||
unsafe {
|
self.reset();
|
||||||
libsqlite3_sys::sqlite3_reset(raw);
|
|
||||||
|
|
||||||
bind_params(scope, raw, params)?;
|
|
||||||
loop {
|
|
||||||
let r = libsqlite3_sys::sqlite3_step(raw);
|
|
||||||
if r == libsqlite3_sys::SQLITE_DONE {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if r != libsqlite3_sys::SQLITE_ROW {
|
|
||||||
return Err(SqliteError::FailedStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last_insert_rowid = db.last_insert_rowid();
|
|
||||||
changes = db.changes();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RunStatementResult {
|
Ok(RunStatementResult {
|
||||||
last_insert_rowid,
|
last_insert_rowid: db.last_insert_rowid(),
|
||||||
changes,
|
changes: db.changes(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,20 +236,15 @@ impl StatementSync {
|
||||||
scope: &mut v8::HandleScope<'a>,
|
scope: &mut v8::HandleScope<'a>,
|
||||||
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
|
||||||
) -> Result<v8::Local<'a, v8::Array>, SqliteError> {
|
) -> Result<v8::Local<'a, v8::Array>, SqliteError> {
|
||||||
let raw = self.inner;
|
|
||||||
|
|
||||||
let mut arr = vec![];
|
let mut arr = vec![];
|
||||||
unsafe {
|
|
||||||
libsqlite3_sys::sqlite3_reset(raw);
|
|
||||||
|
|
||||||
bind_params(scope, raw, params)?;
|
self.bind_params(scope, params)?;
|
||||||
while let Some(result) = read_entry(raw, scope)? {
|
while let Some(result) = self.read_row(scope)? {
|
||||||
arr.push(result.into());
|
arr.push(result.into());
|
||||||
}
|
|
||||||
|
|
||||||
libsqlite3_sys::sqlite3_reset(raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.reset();
|
||||||
|
|
||||||
let arr = v8::Array::new_with_elements(scope, &arr);
|
let arr = v8::Array::new_with_elements(scope, &arr);
|
||||||
Ok(arr)
|
Ok(arr)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue