-
Notifications
You must be signed in to change notification settings - Fork 292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: added D1 integration #246
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a really good start, but I feel that it won't be rust-y enough if we just return JsValue
s. I think the path forward is to create a D1Deserializer
that implements Deserializer. This guide by the Serde developers should be super useful in implementing that, but it still might not be trivial. If you'd like me write that deserializer I'm more than happy to get this PR landed :)
worker/src/d1.rs
Outdated
self.0.error() | ||
} | ||
|
||
pub fn results(&self) -> Vec<JsValue> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should try to avoid returning regular JsValue
s where possible. Perhaps we can use a serde
Deserializer with D1's implicit type conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea
worker/src/d1.rs
Outdated
Ok(D1Result(result.into())) | ||
} | ||
|
||
pub async fn raw(&self) -> Result<Vec<JsValue>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing, let's avoid JsValue
.
worker/src/d1.rs
Outdated
Ok(()) | ||
} | ||
|
||
pub async fn first(&self, col_name: Option<&str>) -> Result<JsValue> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid JsValue
.
I applied your suggestion using Cloudflare's serde_wasm_bindgen, is that ok? |
Yeah that seems fine! When you think this PR is ready, remove the draft status and give me a ping. |
I'm now working on improving the error handling. Some users reported to me that they have problems with some queries and they can't know what is the exact problem because right now it only returns "D1Error". |
pub struct D1PreparedStatement(D1PreparedStatementSys); | ||
|
||
impl D1PreparedStatement { | ||
pub fn bind<T>(&self, value: &T) -> Result<()> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be some variation of pub fn bind<T>(&self, value: &T) -> Result<Result<D1PreparedStatement, JsValue>>
? Please correct me if I'm wrong but it looks like we're throwing away the returned statement / error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally replacing JSValue
with a more appropriate 'Rust-y' type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I need to rework the error handling
Just playing around with this one (as I'm extremely interested in getting in across the line): Looks like I'm having trouble using parameterized queries. A simple code example: // ...
.get_async("/endpoint/:id", |_, ctx| async move {
let default = String::from("A");
let id = ctx.param("id").unwrap_or(&default).clone();
let d1 = ctx.env.d1("mydb")?;
let stmt = match d1
.prepare("SELECT * FROM things WHERE thing_id = ?1")
.bind(&id)
{
Ok(r) => r,
Err(e) => return Response::error(format!("{:?}", e), 500),
};
#[derive(Serialize, Deserialize)]
struct Thing {
thing_id: String,
desc: String,
num: u16,
} Using the above code (and some modifications to the workers create to allow for deeper debugging provides me with this error, irrespective of calls to
Using the same structure but with a non-parameterized query doesn't seem to error in the same way .prepare("SELECT * FROM things WHERE thing_id = 'A'")
.bind(&id) seems to return the expected result of
I've taken a look at serde_wasm_bindgen and it doesn't look like there's anything out of the ordinary here. I've also tried several other different commands / structures and the errors I'm getting are super inconsistent. The errors provided by the binding with JS seem really obscure 🤔 |
Having the same problem as @FlareLine when trying to bind a parameter. |
It does appear that most of the unexpected errors occur when you attempt to use parameterized queries. Ruling out all known issues I still get errors when binding a simple string to a parameter in a query. To get around this issue, I could use simple interpolation instead of parameter binding in my query string, as below: .get_async("/db/:id", |_, ctx| async move {
let default = String::from("A");
let id = ctx.param("id").unwrap_or(&default);
let d1 = ctx.env.d1("my-db")?;
let query = format!("SELECT * FROM things WHERE thing_id = '{}'", id);
let stmt = d1.prepare(&query);
let result = stmt.all().await?;
let things = result.results::<Thing>()?;
Response::from_json::<Vec<Thing>>(&things)
})
.run(request, env)
.await But this opens up the application to SQLi vulnerabilities and is a 'hacky' workaround. Upon attempting to diagnose further, it looks like there's a lack of information coming back from the |
Documenting my findings for referenceI've forked I've changed a significant amount of the error handling within the local I've worked out that one of the underlying errors is actually a
From this, I've noticed that the I'll continue debugging here to see if I can get a consistent pattern working. |
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub type D1PreparedStatement; | ||
|
||
#[wasm_bindgen(structural, method, js_class=D1PreparedStatement, js_name=bind, catch)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes one of the issues documented in #246 (comment). 😄
#[wasm_bindgen(structural, method, js_class=D1PreparedStatement, js_name=bind, catch)] | |
#[wasm_bindgen(structural, method, catch, variadic, js_class=D1PreparedStatement, js_name=bind)] |
T: serde::ser::Serialize + ?Sized, | ||
{ | ||
let value = serde_wasm_bindgen::to_value(value)?; | ||
let array = Array::of1(&value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will most likely cause issues documented in #246 (comment). We should allow bind<T>
to take in an Iterator of T and convert this to an Array
instance for use in our self.0.bind(array)
call. Despite the variadic
fix in the aforementioned comment, I can still only bind a single parameter to my query, and any attempts to call bind
again in some chained fashion will fail.
@KernelFreeze are you still happy to work on this PR? I'd be keen to get this across the line myself - happy to attribute your contributions thus far? 😄 |
Hey! I don't have too much time right now to finish this, so sure, go ahead ^^ If you want to, I can give you access to my repo or if you prefer you can open your own PR. |
@zebp @Alex1607 I've created #270 to supersede this PR as per guidance from @KernelFreeze. |
Thank you! I will try it out as soon as I got some time :D |
Added a Cloudflare D1 integration using wasm_bindgen.
Pending work:
meta
field.