Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions worker-sys/src/types/durable_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use wasm_bindgen::prelude::*;

mod id;
mod namespace;
mod sql_storage;
mod state;
mod storage;
mod transaction;

pub use id::*;
pub use namespace::*;
pub use sql_storage::*;
pub use state::*;
pub use storage::*;
pub use transaction::*;
Expand Down
24 changes: 24 additions & 0 deletions worker-sys/src/types/durable_object/sql_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use js_sys::JsString;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are few more methods for this class that we should implement: https://workers-types.pages.dev/#SqlStorage

#[wasm_bindgen(extends=js_sys::Object)]
pub type DurableObjectSqlStorage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should name this SqlStorage


#[wasm_bindgen(method, catch, js_name=exec, variadic)]
pub fn exec(
this: &DurableObjectSqlStorage,
query: JsString,
args: Vec<JsValue>,
) -> Result<DurableObjectSqlStorageCursor, JsValue>;
}

#[wasm_bindgen]
extern "C" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few more methods that we should implement: https://workers-types.pages.dev/#SqlStorageCursor

#[wasm_bindgen(extends=js_sys::Object)]
pub type DurableObjectSqlStorageCursor;

#[wasm_bindgen(method, catch, js_name=one)]
pub fn one(this: &DurableObjectSqlStorageCursor) -> Result<JsValue, JsValue>;
}
5 changes: 5 additions & 0 deletions worker-sys/src/types/durable_object/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use wasm_bindgen::prelude::*;

use crate::types::DurableObjectTransaction;

use super::DurableObjectSqlStorage;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends=js_sys::Object)]
Expand Down Expand Up @@ -74,4 +76,7 @@ extern "C" {
this: &DurableObjectStorage,
options: js_sys::Object,
) -> Result<js_sys::Promise, JsValue>;

#[wasm_bindgen(method, catch, js_name=sql, getter)]
pub fn sql(this: &DurableObjectStorage) -> Result<DurableObjectSqlStorage, JsValue>;
}
124 changes: 120 additions & 4 deletions worker/src/durable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! [Learn more](https://developers.cloudflare.com/workers/learning/using-durable-objects) about
//! using Durable Objects.

use std::{fmt::Display, ops::Deref, time::Duration};
use std::{convert::Into, fmt::Display, marker::PhantomData, ops::Deref, time::Duration};

use crate::{
date::Date,
Expand All @@ -24,12 +24,13 @@ use crate::{
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use futures_util::Future;
use js_sys::{Map, Number, Object};
use js_sys::{IntoIter, Map, Number, Object};
use serde::{de::DeserializeOwned, Serialize};
use wasm_bindgen::{prelude::*, JsCast};
use worker_sys::{
DurableObject as EdgeDurableObject, DurableObjectId,
DurableObjectNamespace as EdgeObjectNamespace, DurableObjectState, DurableObjectStorage,
DurableObjectNamespace as EdgeObjectNamespace, DurableObjectSqlStorage,
DurableObjectSqlStorageCursor, DurableObjectState, DurableObjectStorage,
DurableObjectTransaction,
};
// use wasm_bindgen_futures::future_to_promise;
Expand Down Expand Up @@ -501,6 +502,121 @@ impl Storage {
.map_err(Error::from)
.map(|_| ())
}

pub fn sql(&self) -> Result<SqlStorage> {
let sql = self.inner.sql()?;
Ok(SqlStorage { inner: sql })
}
}

pub enum SqlStorageValue {
Null,
Boolean(bool),
Integer(i32),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLite integers can be up to 8 bytes (https://sqlite.org/datatype3.html). Any reason not to use i64 here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't losslessly convert to number from an i64 and the DO sql api doesn't seem to support using BigInt for values above Number.MAX_SAFE_INTEGER. I'll ask the DO team for clarification to see if we can support this

Float(f64),
String(String),
Blob(Vec<u8>),
}

impl<T: Into<SqlStorageValue>> From<Option<T>> for SqlStorageValue {
fn from(value: Option<T>) -> Self {
match value {
Some(v) => v.into(),
None => SqlStorageValue::Null,
}
}
}

impl From<bool> for SqlStorageValue {
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}

impl From<f64> for SqlStorageValue {
fn from(value: f64) -> Self {
Self::Float(value)
}
}

impl From<String> for SqlStorageValue {
fn from(value: String) -> Self {
Self::String(value)
}
}

impl From<Vec<u8>> for SqlStorageValue {
fn from(value: Vec<u8>) -> Self {
Self::Blob(value)
}
}

impl From<i32> for SqlStorageValue {
fn from(value: i32) -> Self {
Self::Integer(value)
}
}

impl Into<JsValue> for SqlStorageValue {
fn into(self) -> JsValue {
match self {
SqlStorageValue::Null => JsValue::NULL,
SqlStorageValue::Boolean(b) => b.into(),
SqlStorageValue::Integer(i) => i.into(),
SqlStorageValue::Float(f) => f.into(),
SqlStorageValue::String(s) => s.into(),
SqlStorageValue::Blob(b) => b.into(),
}
}
}

pub struct SqlStorage {
inner: DurableObjectSqlStorage,
}

impl SqlStorage {
pub fn exec<T: DeserializeOwned>(
&self,
query: &str,
params: Vec<SqlStorageValue>,
) -> Result<SqlStorageCursor<T>> {
let cursor = self
.inner
.exec(query.into(), params.into_iter().map(Into::into).collect())?;
let iter = js_sys::try_iter(&cursor)?;
Ok(SqlStorageCursor {
inner: cursor,
iter,
_row_type: PhantomData,
})
}
}

pub struct SqlStorageCursor<T: DeserializeOwned> {
inner: DurableObjectSqlStorageCursor,
iter: Option<IntoIter>,
_row_type: PhantomData<T>,
}

impl<T: DeserializeOwned> SqlStorageCursor<T> {
pub fn one(&self) -> Result<T> {
self.inner
.one()
.and_then(|row| serde_wasm_bindgen::from_value(row).map_err(JsValue::from))
.map_err(Error::from)
}
}

impl<T: DeserializeOwned> Iterator for SqlStorageCursor<T> {
type Item = Result<T>;

fn next(&mut self) -> Option<Self::Item> {
let row = self.iter.as_mut()?.next()?;
Some(
row.and_then(|row| serde_wasm_bindgen::from_value(row).map_err(JsValue::from))
.map_err(Error::from),
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to add support for toArray and raw as well? https://developers.cloudflare.com/durable-objects/api/storage-api/#returns

}

pub struct Transaction {
Expand All @@ -515,7 +631,7 @@ impl Transaction {
if val.is_undefined() {
Err(JsValue::from("No such value in storage."))
} else {
serde_wasm_bindgen::from_value(val).map_err(std::convert::Into::into)
serde_wasm_bindgen::from_value(val).map_err(Into::into)
}
})
.map_err(Error::from)
Expand Down