diff --git a/Cargo.lock b/Cargo.lock index c9dc91badd04..9a80d864b6ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,7 +1106,6 @@ dependencies = [ "num-bigint", "once_cell", "pin-project", - "psl", "quaint", "serde", "serde-wasm-bindgen", @@ -1118,6 +1117,7 @@ dependencies = [ "uuid", "wasm-bindgen", "wasm-bindgen-futures", + "web-sys", ] [[package]] diff --git a/psl/psl-core/src/datamodel_connector.rs b/psl/psl-core/src/datamodel_connector.rs index 242f0df20b7c..dc3a7e80bd10 100644 --- a/psl/psl-core/src/datamodel_connector.rs +++ b/psl/psl-core/src/datamodel_connector.rs @@ -361,10 +361,7 @@ pub trait Connector: Send + Sync { } } -#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] -#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Deserialize)] pub enum Flavour { - #[default] Cockroach, Mongo, Sqlserver, diff --git a/query-engine/driver-adapters/Cargo.toml b/query-engine/driver-adapters/Cargo.toml index ec77df85e142..23244ab7dcb4 100644 --- a/query-engine/driver-adapters/Cargo.toml +++ b/query-engine/driver-adapters/Cargo.toml @@ -8,7 +8,6 @@ async-trait = "0.1" once_cell = "1.15" serde.workspace = true serde_json.workspace = true -psl.workspace = true tracing = "0.1" tracing-core = "0.1" metrics = "0.18" @@ -21,6 +20,7 @@ num-bigint = "0.4.3" bigdecimal = "0.3.0" chrono = "0.4.20" futures = "0.3" +web-sys = "0.3.65" [dev-dependencies] expect-test = "1" diff --git a/query-engine/driver-adapters/src/queryable/mod.rs b/query-engine/driver-adapters/src/queryable/mod.rs index ac252bbb011b..9cd2eb1c9b33 100644 --- a/query-engine/driver-adapters/src/queryable/mod.rs +++ b/query-engine/driver-adapters/src/queryable/mod.rs @@ -19,11 +19,11 @@ pub(crate) use wasm::JsBaseQueryable; use super::{ conversion, proxy::{CommonProxy, DriverProxy, Query}, + types::AdapterFlavour, }; use crate::send_future::SendFuture; use async_trait::async_trait; use futures::Future; -use psl::datamodel_connector::Flavour; use quaint::{ connector::{metrics, IsolationLevel, Transaction}, error::{Error, ErrorKind}, @@ -34,17 +34,16 @@ use tracing::{info_span, Instrument}; impl JsBaseQueryable { pub(crate) fn new(proxy: CommonProxy) -> Self { - let flavour: Flavour = proxy.flavour.parse().unwrap(); + let flavour: AdapterFlavour = proxy.flavour.parse().unwrap(); Self { proxy, flavour } } /// visit a quaint query AST according to the flavour of the JS connector fn visit_quaint_query<'a>(&self, q: QuaintQuery<'a>) -> quaint::Result<(String, Vec>)> { match self.flavour { - Flavour::Mysql => visitor::Mysql::build(q), - Flavour::Postgres => visitor::Postgres::build(q), - Flavour::Sqlite => visitor::Sqlite::build(q), - _ => unimplemented!("Unsupported flavour for JS connector {:?}", self.flavour), + AdapterFlavour::Mysql => visitor::Mysql::build(q), + AdapterFlavour::Postgres => visitor::Postgres::build(q), + AdapterFlavour::Sqlite => visitor::Sqlite::build(q), } } @@ -52,10 +51,9 @@ impl JsBaseQueryable { let sql: String = sql.to_string(); let converter = match self.flavour { - Flavour::Postgres => conversion::postgres::value_to_js_arg, - Flavour::Sqlite => conversion::sqlite::value_to_js_arg, - Flavour::Mysql => conversion::mysql::value_to_js_arg, - _ => unreachable!("Unsupported flavour for JS connector {:?}", self.flavour), + AdapterFlavour::Postgres => conversion::postgres::value_to_js_arg, + AdapterFlavour::Sqlite => conversion::sqlite::value_to_js_arg, + AdapterFlavour::Mysql => conversion::mysql::value_to_js_arg, }; let args = values @@ -127,7 +125,7 @@ impl QuaintQueryable for JsBaseQueryable { return Err(Error::builder(ErrorKind::invalid_isolation_level(&isolation_level)).build()); } - if self.flavour == Flavour::Sqlite { + if self.flavour == AdapterFlavour::Sqlite { return match isolation_level { IsolationLevel::Serializable => Ok(()), _ => Err(Error::builder(ErrorKind::invalid_isolation_level(&isolation_level)).build()), @@ -140,9 +138,8 @@ impl QuaintQueryable for JsBaseQueryable { fn requires_isolation_first(&self) -> bool { match self.flavour { - Flavour::Mysql => true, - Flavour::Postgres | Flavour::Sqlite => false, - _ => unreachable!(), + AdapterFlavour::Mysql => true, + AdapterFlavour::Postgres | AdapterFlavour::Sqlite => false, } } } diff --git a/query-engine/driver-adapters/src/queryable/wasm.rs b/query-engine/driver-adapters/src/queryable/wasm.rs index 867d1fb5081a..ee1c65a81347 100644 --- a/query-engine/driver-adapters/src/queryable/wasm.rs +++ b/query-engine/driver-adapters/src/queryable/wasm.rs @@ -1,6 +1,6 @@ +use crate::types::AdapterFlavour; use crate::wasm::proxy::{CommonProxy, DriverProxy}; use crate::{JsObjectExtern, JsQueryable}; -use psl::datamodel_connector::Flavour; use wasm_bindgen::prelude::wasm_bindgen; /// A JsQueryable adapts a Proxy to implement quaint's Queryable interface. It has the @@ -16,10 +16,9 @@ use wasm_bindgen::prelude::wasm_bindgen; /// into a `quaint::connector::result_set::ResultSet`. A quaint `ResultSet` is basically a vector /// of `quaint::Value` but said type is a tagged enum, with non-unit variants that cannot be converted to javascript as is. #[wasm_bindgen(getter_with_clone)] -#[derive(Default)] pub(crate) struct JsBaseQueryable { pub(crate) proxy: CommonProxy, - pub flavour: Flavour, + pub flavour: AdapterFlavour, } pub fn from_wasm(driver: JsObjectExtern) -> JsQueryable { diff --git a/query-engine/driver-adapters/src/types.rs b/query-engine/driver-adapters/src/types.rs index 2b2c0b45e50c..8975e7cd9044 100644 --- a/query-engine/driver-adapters/src/types.rs +++ b/query-engine/driver-adapters/src/types.rs @@ -1,5 +1,7 @@ #![allow(unused_imports)] +use std::str::FromStr; + #[cfg(not(target_arch = "wasm32"))] use napi::bindgen_prelude::{FromNapiValue, ToNapiValue}; @@ -9,6 +11,28 @@ use tsify::Tsify; use crate::conversion::JSArg; use serde::{Deserialize, Serialize}; +#[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize, Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum AdapterFlavour { + Mysql, + Postgres, + Sqlite, +} + +impl FromStr for AdapterFlavour { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "postgres" => Ok(Self::Postgres), + "mysql" => Ok(Self::Mysql), + "sqlite" => Ok(Self::Sqlite), + _ => Err(format!("Unsupported adapter flavour: {:?}", s)), + } + } +} + /// This result set is more convenient to be manipulated from both Rust and NodeJS. /// Quaint's version of ResultSet is: /// @@ -27,7 +51,7 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize, Tsify))] #[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] #[cfg_attr(target_arch = "wasm32", serde(rename_all = "camelCase"))] -#[derive(Debug, Default)] +#[derive(Debug)] pub struct JSResultSet { pub column_types: Vec, pub column_names: Vec, @@ -190,6 +214,7 @@ pub struct Query { #[cfg_attr(not(target_arch = "wasm32"), napi_derive::napi(object))] #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize, Tsify))] #[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] +#[cfg_attr(target_arch = "wasm32", serde(rename_all = "camelCase"))] #[derive(Debug, Default)] pub struct TransactionOptions { /// Whether or not to run a phantom query (i.e., a query that only influences Prisma event logs, but not the database itself) diff --git a/query-engine/driver-adapters/src/wasm/async_js_function.rs b/query-engine/driver-adapters/src/wasm/async_js_function.rs index f4e3771694a2..e13f288f4a56 100644 --- a/query-engine/driver-adapters/src/wasm/async_js_function.rs +++ b/query-engine/driver-adapters/src/wasm/async_js_function.rs @@ -1,19 +1,21 @@ -use js_sys::{Function as JsFunction, Promise as JsPromise}; -use serde::{de::DeserializeOwned, Serialize}; +use js_sys::{Function as JsFunction, JsString, Promise as JsPromise}; +use serde::Serialize; use std::marker::PhantomData; +use std::str::FromStr; use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::describe::WasmDescribe; use wasm_bindgen::{JsError, JsValue}; use wasm_bindgen_futures::JsFuture; use super::error::into_quaint_error; +use super::from_js::FromJsValue; use super::result::JsResult; -#[derive(Clone, Default)] +#[derive(Clone)] pub(crate) struct AsyncJsFunction where ArgType: Serialize, - ReturnType: DeserializeOwned, + ReturnType: FromJsValue, { pub threadsafe_fn: JsFunction, @@ -24,7 +26,7 @@ where impl From for AsyncJsFunction where T: Serialize, - R: DeserializeOwned, + R: FromJsValue, { fn from(js_fn: JsFunction) -> Self { Self { @@ -38,14 +40,20 @@ where impl AsyncJsFunction where T: Serialize, - R: DeserializeOwned, + R: FromJsValue, { pub async fn call(&self, arg1: T) -> quaint::Result { let result = self.call_internal(arg1).await; match result { - Ok(js_result) => js_result.into(), - Err(err) => Err(into_quaint_error(err)), + Ok(js_result) => { + web_sys::console::log_1(&JsString::from_str("OK JS").unwrap().into()); + js_result.into() + } + Err(err) => { + web_sys::console::log_1(&JsString::from_str("CALL ERR").unwrap().into()); + Err(into_quaint_error(err)) + } } } @@ -54,7 +62,7 @@ where let promise = self.threadsafe_fn.call1(&JsValue::null(), &arg1)?; let future = JsFuture::from(JsPromise::from(promise)); let value = future.await?; - let js_result: JsResult = value.try_into()?; + let js_result = JsResult::::from_js_value(value)?; Ok(js_result) } @@ -63,7 +71,7 @@ where impl WasmDescribe for AsyncJsFunction where ArgType: Serialize, - ReturnType: DeserializeOwned, + ReturnType: FromJsValue, { fn describe() { JsFunction::describe(); @@ -73,7 +81,7 @@ where impl FromWasmAbi for AsyncJsFunction where ArgType: Serialize, - ReturnType: DeserializeOwned, + ReturnType: FromJsValue, { type Abi = ::Abi; diff --git a/query-engine/driver-adapters/src/wasm/error.rs b/query-engine/driver-adapters/src/wasm/error.rs index 0aa4fe7981f2..49d7ad6cf440 100644 --- a/query-engine/driver-adapters/src/wasm/error.rs +++ b/query-engine/driver-adapters/src/wasm/error.rs @@ -5,6 +5,7 @@ use wasm_bindgen::JsValue; /// transforms a Wasm error into a Quaint error pub(crate) fn into_quaint_error(wasm_err: JsValue) -> QuaintError { let status = "WASM_ERROR".to_string(); + web_sys::console::log_1(&wasm_err); let reason = Reflect::get(&wasm_err, &JsValue::from_str("stack")) .ok() .and_then(|value| value.as_string()) diff --git a/query-engine/driver-adapters/src/wasm/from_js.rs b/query-engine/driver-adapters/src/wasm/from_js.rs new file mode 100644 index 000000000000..aaa0d91223f6 --- /dev/null +++ b/query-engine/driver-adapters/src/wasm/from_js.rs @@ -0,0 +1,15 @@ +use serde::de::DeserializeOwned; +use wasm_bindgen::JsValue; + +pub trait FromJsValue: Sized { + fn from_js_value(value: JsValue) -> Result; +} + +impl FromJsValue for T +where + T: DeserializeOwned, +{ + fn from_js_value(value: JsValue) -> Result { + serde_wasm_bindgen::from_value(value).map_err(|e| JsValue::from(e)) + } +} diff --git a/query-engine/driver-adapters/src/wasm/mod.rs b/query-engine/driver-adapters/src/wasm/mod.rs index 9cdc66b177e7..2afe1987e1a7 100644 --- a/query-engine/driver-adapters/src/wasm/mod.rs +++ b/query-engine/driver-adapters/src/wasm/mod.rs @@ -2,6 +2,7 @@ mod async_js_function; mod error; +mod from_js; mod js_object_extern; pub(crate) mod proxy; mod result; diff --git a/query-engine/driver-adapters/src/wasm/proxy.rs b/query-engine/driver-adapters/src/wasm/proxy.rs index bb2e9a855fe7..16a88b8d1fd3 100644 --- a/query-engine/driver-adapters/src/wasm/proxy.rs +++ b/query-engine/driver-adapters/src/wasm/proxy.rs @@ -15,7 +15,6 @@ type JsResult = core::result::Result; /// querying and executing SQL (i.e. a client connector). The Proxy uses Wasm's JsFunction to /// invoke the code within the node runtime that implements the client connector. #[wasm_bindgen(getter_with_clone)] -#[derive(Default)] pub(crate) struct CommonProxy { /// Execute a query given as SQL, interpolating the given parameters. query_raw: AsyncJsFunction, @@ -38,7 +37,6 @@ pub(crate) struct DriverProxy { /// This a JS proxy for accessing the methods, specific /// to JS transaction objects #[wasm_bindgen(getter_with_clone)] -#[derive(Default)] pub(crate) struct TransactionProxy { /// transaction options options: TransactionOptions, diff --git a/query-engine/driver-adapters/src/wasm/result.rs b/query-engine/driver-adapters/src/wasm/result.rs index df4652307469..fc5115e4a500 100644 --- a/query-engine/driver-adapters/src/wasm/result.rs +++ b/query-engine/driver-adapters/src/wasm/result.rs @@ -1,8 +1,10 @@ -use js_sys::Boolean as JsBoolean; +use std::str::FromStr; + +use js_sys::{Boolean as JsBoolean, JsString}; use quaint::error::{Error as QuaintError, ErrorKind}; -use serde::de::DeserializeOwned; use wasm_bindgen::{JsCast, JsValue}; +use super::from_js::FromJsValue; use crate::{error::DriverAdapterError, JsObjectExtern}; impl From for QuaintError { @@ -26,28 +28,17 @@ impl From for QuaintError { /// Wrapper for JS-side result type pub(crate) enum JsResult where - T: DeserializeOwned, + T: FromJsValue, { Ok(T), Err(DriverAdapterError), } -impl TryFrom for JsResult -where - T: DeserializeOwned, -{ - type Error = JsValue; - - fn try_from(value: JsValue) -> Result { - Self::from_js_unknown(value) - } -} - -impl JsResult +impl FromJsValue for JsResult where - T: DeserializeOwned, + T: FromJsValue, { - fn from_js_unknown(unknown: JsValue) -> Result { + fn from_js_value(unknown: JsValue) -> Result { let object = unknown.unchecked_into::(); let ok: JsBoolean = object.get("ok".into())?.unchecked_into(); @@ -55,7 +46,9 @@ where if ok { let js_value: JsValue = object.get("value".into())?; - let deserialized = serde_wasm_bindgen::from_value::(js_value)?; + web_sys::console::log_1(&JsString::from_str("BEFORE DESERIALIZE").unwrap().into()); + let deserialized = T::from_js_value(js_value)?; + web_sys::console::log_1(&JsString::from_str(" DESERIALIZE").unwrap().into()); return Ok(Self::Ok(deserialized)); } @@ -67,7 +60,7 @@ where impl From> for quaint::Result where - T: DeserializeOwned, + T: FromJsValue, { fn from(value: JsResult) -> Self { match value { diff --git a/query-engine/driver-adapters/src/wasm/transaction.rs b/query-engine/driver-adapters/src/wasm/transaction.rs index 43925b488101..d1b93bd4bfd0 100644 --- a/query-engine/driver-adapters/src/wasm/transaction.rs +++ b/query-engine/driver-adapters/src/wasm/transaction.rs @@ -1,23 +1,25 @@ use async_trait::async_trait; +use js_sys::{JsString, Object as JsObject}; use metrics::decrement_gauge; use quaint::{ connector::{IsolationLevel, Transaction as QuaintTransaction}, prelude::{Query as QuaintQuery, Queryable, ResultSet}, Value, }; -use serde::Deserialize; +use std::str::FromStr; +use wasm_bindgen::JsCast; -use super::proxy::{TransactionOptions, TransactionProxy}; -use crate::{queryable::JsBaseQueryable, send_future::SendFuture}; +use super::{ + from_js::FromJsValue, + proxy::{TransactionOptions, TransactionProxy}, +}; +use crate::{proxy::CommonProxy, queryable::JsBaseQueryable, send_future::SendFuture, JsObjectExtern}; // Wrapper around JS transaction objects that implements Queryable // and quaint::Transaction. Can be used in place of quaint transaction, // but delegates most operations to JS -#[derive(Deserialize, Default)] pub(crate) struct JsTransaction { - #[serde(skip)] tx_proxy: TransactionProxy, - #[serde(skip)] inner: JsBaseQueryable, } @@ -36,6 +38,21 @@ impl JsTransaction { } } +impl FromJsValue for JsTransaction { + fn from_js_value(value: wasm_bindgen::prelude::JsValue) -> Result { + let object: JsObjectExtern = value.dyn_into::()?.unchecked_into(); + web_sys::console::log_1(&JsString::from_str("OBJECT").unwrap().into()); + let common_proxy = CommonProxy::new(&object)?; + web_sys::console::log_1(&JsString::from_str("PROXY").unwrap().into()); + let base = JsBaseQueryable::new(common_proxy); + web_sys::console::log_1(&JsString::from_str("BASE").unwrap().into()); + let tx_proxy = TransactionProxy::new(&object)?; + web_sys::console::log_1(&JsString::from_str("TX_PROXY").unwrap().into()); + + Ok(Self::new(base, tx_proxy)) + } +} + #[async_trait] impl QuaintTransaction for JsTransaction { async fn commit(&self) -> quaint::Result<()> {