Bypass deserialisation for sql::Value responses on Surreal methods ()

This commit is contained in:
Rushmore Mushambi 2023-04-28 13:20:57 +02:00 committed by GitHub
parent eeb46468aa
commit 141eb091a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 543 additions and 267 deletions

View file

@ -105,7 +105,7 @@ url = "2.3.1"
env_logger = "0.10.0"
temp-dir = "0.1.11"
time = { version = "0.3.20", features = ["serde"] }
tokio = { version = "1.27.0", features = ["macros", "rt", "rt-multi-thread"] }
tokio = { version = "1.27.0", features = ["macros", "sync", "rt-multi-thread"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
pharos = "0.5.3"

View file

@ -79,7 +79,7 @@ async fn main() -> surrealdb::Result<()> {
db.use_ns("namespace").use_db("database").await?;
// Create a new person with a random ID
let tobie: Person = db
let tobie: Vec<Person> = db
.create("person")
.content(Person {
id: None,
@ -92,10 +92,8 @@ async fn main() -> surrealdb::Result<()> {
})
.await?;
assert!(tobie.id.is_some());
// Create a new person with a specific ID
let mut jaime: Person = db
let mut jaime: Option<Person> = db
.create(("person", "jaime"))
.content(Person {
id: None,
@ -108,21 +106,15 @@ async fn main() -> surrealdb::Result<()> {
})
.await?;
assert_eq!(jaime.id.unwrap().to_string(), "person:jaime");
// Update a person record with a specific ID
jaime = db
.update(("person", "jaime"))
.merge(json!({ "marketing": true }))
.await?;
assert!(jaime.marketing);
// Select all people records
let people: Vec<Person> = db.select("person").await?;
assert!(!people.is_empty());
// Perform a custom advanced query
let sql = r#"
SELECT marketing, count()
@ -134,13 +126,11 @@ async fn main() -> surrealdb::Result<()> {
.bind(("table", "person"))
.await?;
dbg!(groups);
// Delete all people upto but not including Jaime
db.delete("person").range(.."jaime").await?;
let people: Vec<Person> = db.delete("person").range(.."jaime").await?;
// Delete all people
db.delete("person").await?;
let people: Vec<Person> = db.delete("person").await?;
Ok(())
}

View file

@ -14,7 +14,7 @@ pub struct Person {
}
#[post("/person/{id}")]
pub async fn create(id: Path<String>, person: Json<Person>) -> Result<Json<Person>, Error> {
pub async fn create(id: Path<String>, person: Json<Person>) -> Result<Json<Option<Person>>, Error> {
let person = DB.create((PERSON, &*id)).content(person).await?;
Ok(Json(person))
}
@ -26,7 +26,7 @@ pub async fn read(id: Path<String>) -> Result<Json<Option<Person>>, Error> {
}
#[put("/person/{id}")]
pub async fn update(id: Path<String>, person: Json<Person>) -> Result<Json<Person>, Error> {
pub async fn update(id: Path<String>, person: Json<Person>) -> Result<Json<Option<Person>>, Error> {
let person = DB.update((PERSON, &*id)).content(person).await?;
Ok(Json(person))
}

View file

@ -20,7 +20,7 @@ pub async fn create(
db: Db,
id: Path<String>,
Json(person): Json<Person>,
) -> Result<Json<Person>, Error> {
) -> Result<Json<Option<Person>>, Error> {
let person = db.create((PERSON, &*id)).content(person).await?;
Ok(Json(person))
}
@ -34,7 +34,7 @@ pub async fn update(
db: Db,
id: Path<String>,
Json(person): Json<Person>,
) -> Result<Json<Person>, Error> {
) -> Result<Json<Option<Person>>, Error> {
let person = db.update((PERSON, &*id)).content(person).await?;
Ok(Json(person))
}

View file

@ -1,4 +1,5 @@
use crate::api;
use crate::api::err::Error;
use crate::api::method::query::Response;
use crate::api::opt::Endpoint;
use crate::api::ExtraFeatures;
@ -170,17 +171,14 @@ pub trait Connection: Sized + Send + Sync + 'static {
Self: api::Connection;
/// Receive responses for all methods except `query`
fn recv<R>(
fn recv(
&mut self,
receiver: Receiver<Result<DbResponse>>,
) -> Pin<Box<dyn Future<Output = Result<R>> + Send + Sync + '_>>
where
R: DeserializeOwned,
{
) -> Pin<Box<dyn Future<Output = Result<Value>> + Send + Sync + '_>> {
Box::pin(async move {
let response = receiver.into_recv_async().await?;
match response? {
DbResponse::Other(value) => from_value(value).map_err(Into::into),
DbResponse::Other(value) => Ok(value),
DbResponse::Query(..) => unreachable!(),
}
})
@ -209,6 +207,85 @@ pub trait Connection: Sized + Send + Sync + 'static {
where
R: DeserializeOwned,
Self: api::Connection,
{
Box::pin(async move {
let rx = self.send(router, param).await?;
let value = self.recv(rx).await?;
from_value(value).map_err(Into::into)
})
}
/// Execute methods that return an optional single response
fn execute_opt<'r, R>(
&'r mut self,
router: &'r Router<Self>,
param: Param,
) -> Pin<Box<dyn Future<Output = Result<Option<R>>> + Send + Sync + 'r>>
where
R: DeserializeOwned,
Self: api::Connection,
{
Box::pin(async move {
let rx = self.send(router, param).await?;
match self.recv(rx).await? {
Value::None | Value::Null => Ok(None),
value => from_value(value).map_err(Into::into),
}
})
}
/// Execute methods that return multiple responses
fn execute_vec<'r, R>(
&'r mut self,
router: &'r Router<Self>,
param: Param,
) -> Pin<Box<dyn Future<Output = Result<Vec<R>>> + Send + Sync + 'r>>
where
R: DeserializeOwned,
Self: api::Connection,
{
Box::pin(async move {
let rx = self.send(router, param).await?;
let value = match self.recv(rx).await? {
Value::None | Value::Null => Value::Array(Default::default()),
Value::Array(array) => Value::Array(array),
value => vec![value].into(),
};
from_value(value).map_err(Into::into)
})
}
/// Execute methods that return nothing
fn execute_unit<'r>(
&'r mut self,
router: &'r Router<Self>,
param: Param,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + Sync + 'r>>
where
Self: api::Connection,
{
Box::pin(async move {
let rx = self.send(router, param).await?;
match self.recv(rx).await? {
Value::None | Value::Null => Ok(()),
Value::Array(array) if array.is_empty() => Ok(()),
value => Err(Error::FromValue {
value,
error: "expected the database to return nothing".to_owned(),
}
.into()),
}
})
}
/// Execute methods that return a raw value
fn execute_value<'r>(
&'r mut self,
router: &'r Router<Self>,
param: Param,
) -> Pin<Box<dyn Future<Output = Result<Value>> + Send + Sync + 'r>>
where
Self: api::Connection,
{
Box::pin(async move {
let rx = self.send(router, param).await?;

View file

@ -37,7 +37,7 @@
//! db.use_ns("namespace").use_db("database").await?;
//!
//! // Create a new person with a random ID
//! let created: Person = db.create("person")
//! let created: Vec<Person> = db.create("person")
//! .content(Person {
//! title: "Founder & CEO".into(),
//! name: Name {
@ -49,7 +49,7 @@
//! .await?;
//!
//! // Create a new person with a specific ID
//! let created: Person = db.create(("person", "jaime"))
//! let created: Option<Person> = db.create(("person", "jaime"))
//! .content(Person {
//! title: "Founder & COO".into(),
//! name: Name {
@ -61,7 +61,7 @@
//! .await?;
//!
//! // Update a person record with a specific ID
//! let updated: Person = db.update(("person", "jaime"))
//! let updated: Option<Person> = db.update(("person", "jaime"))
//! .merge(json!({"marketing": true}))
//! .await?;
//!

View file

@ -32,7 +32,7 @@ where
return Err(Error::AuthNotSupported.into());
}
let mut conn = Client::new(Method::Authenticate);
conn.execute(router, Param::new(vec![self.token.into()])).await
conn.execute_unit(router, Param::new(vec![self.token.into()])).await
})
}
}

View file

@ -7,6 +7,7 @@ use crate::api::Connection;
use crate::api::Result;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::future::Future;
@ -28,32 +29,61 @@ pub struct Content<'r, C: Connection, D, R> {
pub(super) response_type: PhantomData<R>,
}
impl<'r, Client, D, R> IntoFuture for Content<'r, Client, D, R>
macro_rules! into_future {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Content {
router,
method,
resource,
range,
content,
..
} = self;
let content = to_value(content);
Box::pin(async move {
let param = match range {
Some(range) => resource?.with_range(range)?,
None => resource?.into(),
};
let mut conn = Client::new(method);
conn.$method(router?, Param::new(vec![param, content?])).await
})
}
};
}
impl<'r, Client, D> IntoFuture for Content<'r, Client, D, Value>
where
Client: Connection,
D: Serialize,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, D, R> IntoFuture for Content<'r, Client, D, Option<R>>
where
Client: Connection,
D: Serialize,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
fn into_future(self) -> Self::IntoFuture {
let Content {
router,
method,
resource,
range,
content,
..
} = self;
let content = to_value(content);
Box::pin(async move {
let param = match range {
Some(range) => resource?.with_range(range)?,
None => resource?.into(),
};
let mut conn = Client::new(method);
conn.execute(router?, Param::new(vec![param, content?])).await
})
}
into_future! {execute_opt}
}
impl<'r, Client, D, R> IntoFuture for Content<'r, Client, D, Vec<R>>
where
Client: Connection,
D: Serialize,
R: DeserializeOwned,
{
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_vec}
}

View file

@ -5,6 +5,7 @@ use crate::api::method::Content;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::sql::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::future::Future;
@ -22,7 +23,7 @@ pub struct Create<'r, C: Connection, R> {
}
macro_rules! into_future {
() => {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Create {
router,
@ -31,21 +32,31 @@ macro_rules! into_future {
} = self;
Box::pin(async {
let mut conn = Client::new(Method::Create);
conn.execute(router?, Param::new(vec![resource?.into()])).await
conn.$method(router?, Param::new(vec![resource?.into()])).await
})
}
};
}
impl<'r, Client> IntoFuture for Create<'r, Client, Value>
where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, R> IntoFuture for Create<'r, Client, Option<R>>
where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_opt}
}
impl<'r, Client, R> IntoFuture for Create<'r, Client, Vec<R>>
@ -53,47 +64,28 @@ where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_vec}
}
macro_rules! content {
($this:ident, $data:ident) => {
impl<'r, C, R> Create<'r, C, R>
where
C: Connection,
{
/// Sets content of a record
pub fn content<D>(self, data: D) -> Content<'r, C, D, R>
where
D: Serialize,
{
Content {
router: $this.router,
router: self.router,
method: Method::Create,
resource: $this.resource,
resource: self.resource,
range: None,
content: $data,
content: data,
response_type: PhantomData,
}
};
}
impl<'r, C, R> Create<'r, C, Option<R>>
where
C: Connection,
{
/// Sets content of a record
pub fn content<D>(self, data: D) -> Content<'r, C, D, R>
where
D: Serialize,
{
content!(self, data)
}
}
impl<'r, C, R> Create<'r, C, Vec<R>>
where
C: Connection,
{
/// Sets content of a record
pub fn content<D>(self, data: D) -> Content<'r, C, D, R>
where
D: Serialize,
{
content!(self, data)
}
}

View file

@ -6,6 +6,7 @@ use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::sql::Id;
use crate::sql::Value;
use serde::de::DeserializeOwned;
use std::future::Future;
use std::future::IntoFuture;
@ -23,7 +24,7 @@ pub struct Delete<'r, C: Connection, R> {
}
macro_rules! into_future {
() => {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Delete {
router,
@ -37,21 +38,31 @@ macro_rules! into_future {
None => resource?.into(),
};
let mut conn = Client::new(Method::Delete);
conn.execute(router?, Param::new(vec![param])).await
conn.$method(router?, Param::new(vec![param])).await
})
}
};
}
impl<'r, Client> IntoFuture for Delete<'r, Client, Value>
where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, R> IntoFuture for Delete<'r, Client, Option<R>>
where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_opt}
}
impl<'r, Client, R> IntoFuture for Delete<'r, Client, Vec<R>>
@ -62,7 +73,18 @@ where
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_vec}
}
impl<C> Delete<'_, C, Value>
where
C: Connection,
{
/// Restricts a range of records to delete
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
self
}
}
impl<C, R> Delete<'_, C, Vec<R>>

View file

@ -32,7 +32,7 @@ where
return Err(Error::BackupsNotSupported.into());
}
let mut conn = Client::new(Method::Export);
conn.execute(router, Param::file(self.file)).await
conn.execute_unit(router, Param::file(self.file)).await
})
}
}

View file

@ -24,7 +24,7 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async {
let mut conn = Client::new(Method::Health);
conn.execute(self.router?, Param::new(Vec::new())).await
conn.execute_unit(self.router?, Param::new(Vec::new())).await
})
}
}

View file

@ -32,7 +32,7 @@ where
return Err(Error::BackupsNotSupported.into());
}
let mut conn = Client::new(Method::Import);
conn.execute(router, Param::file(self.file)).await
conn.execute_unit(router, Param::file(self.file)).await
})
}
}

View file

@ -30,7 +30,7 @@ where
return Err(Error::AuthNotSupported.into());
}
let mut conn = Client::new(Method::Invalidate);
conn.execute(router, Param::new(Vec::new())).await
conn.execute_unit(router, Param::new(Vec::new())).await
})
}
}

View file

@ -26,7 +26,7 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut conn = Client::new(Method::Kill);
conn.execute(self.router?, Param::new(vec![self.query_id.into()])).await
conn.execute_unit(self.router?, Param::new(vec![self.query_id.into()])).await
})
}
}

View file

@ -7,6 +7,7 @@ use crate::api::Connection;
use crate::api::Result;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::future::Future;
@ -25,31 +26,60 @@ pub struct Merge<'r, C: Connection, D, R> {
pub(super) response_type: PhantomData<R>,
}
impl<'r, Client, D, R> IntoFuture for Merge<'r, Client, D, R>
macro_rules! into_future {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Merge {
router,
resource,
range,
content,
..
} = self;
let content = to_value(content);
Box::pin(async move {
let param = match range {
Some(range) => resource?.with_range(range)?,
None => resource?.into(),
};
let mut conn = Client::new(Method::Merge);
conn.$method(router?, Param::new(vec![param, content?])).await
})
}
};
}
impl<'r, Client, D> IntoFuture for Merge<'r, Client, D, Value>
where
Client: Connection,
D: Serialize,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, D, R> IntoFuture for Merge<'r, Client, D, Option<R>>
where
Client: Connection,
D: Serialize,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
fn into_future(self) -> Self::IntoFuture {
let Merge {
router,
resource,
range,
content,
..
} = self;
let content = to_value(content);
Box::pin(async move {
let param = match range {
Some(range) => resource?.with_range(range)?,
None => resource?.into(),
};
let mut conn = Client::new(Method::Merge);
conn.execute(router?, Param::new(vec![param, content?])).await
})
}
into_future! {execute_opt}
}
impl<'r, Client, D, R> IntoFuture for Merge<'r, Client, D, Vec<R>>
where
Client: Connection,
D: Serialize,
R: DeserializeOwned,
{
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_vec}
}

View file

@ -637,7 +637,7 @@ where
/// let person: Option<Person> = db.select(("person", "h5wxrf2ewk8xjxosxtyc")).await?;
///
/// // You can skip an unnecessary option if you know the record already exists
/// let person: Person = db.select(("person", "h5wxrf2ewk8xjxosxtyc")).await?;
/// let person: Option<Person> = db.select(("person", "h5wxrf2ewk8xjxosxtyc")).await?;
/// #
/// # Ok(())
/// # }
@ -681,10 +681,10 @@ where
/// db.use_ns("namespace").use_db("database").await?;
///
/// // Create a record with a random ID
/// let person: Person = db.create("person").await?;
/// let person: Vec<Person> = db.create("person").await?;
///
/// // Create a record with a specific ID
/// let record: Person = db.create(("person", "tobie"))
/// let record: Option<Person> = db.create(("person", "tobie"))
/// .content(User {
/// name: "Tobie",
/// settings: Settings {

View file

@ -27,36 +27,63 @@ pub struct Patch<'r, C: Connection, R> {
pub(super) response_type: PhantomData<R>,
}
impl<'r, Client, R> IntoFuture for Patch<'r, Client, R>
macro_rules! into_future {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Patch {
router,
resource,
range,
patches,
..
} = self;
Box::pin(async move {
let param = match range {
Some(range) => resource?.with_range(range)?,
None => resource?.into(),
};
let mut vec = Vec::with_capacity(patches.len());
for result in patches {
vec.push(result?);
}
let patches = Value::Array(Array(vec));
let mut conn = Client::new(Method::Patch);
conn.$method(router?, Param::new(vec![param, patches])).await
})
}
};
}
impl<'r, Client> IntoFuture for Patch<'r, Client, Value>
where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, R> IntoFuture for Patch<'r, Client, Option<R>>
where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
fn into_future(self) -> Self::IntoFuture {
let Patch {
router,
resource,
range,
patches,
..
} = self;
Box::pin(async move {
let param = match range {
Some(range) => resource?.with_range(range)?,
None => resource?.into(),
};
let mut vec = Vec::with_capacity(patches.len());
for result in patches {
vec.push(result?);
}
let patches = Value::Array(Array(vec));
let mut conn = Client::new(Method::Patch);
conn.execute(router?, Param::new(vec![param, patches])).await
})
}
into_future! {execute_opt}
}
impl<'r, Client, R> IntoFuture for Patch<'r, Client, Vec<R>>
where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_vec}
}
impl<'r, C, R> Patch<'r, C, R>

View file

@ -6,6 +6,7 @@ use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::sql::Id;
use crate::sql::Value;
use serde::de::DeserializeOwned;
use std::future::Future;
use std::future::IntoFuture;
@ -23,7 +24,7 @@ pub struct Select<'r, C: Connection, R> {
}
macro_rules! into_future {
() => {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Select {
router,
@ -37,21 +38,31 @@ macro_rules! into_future {
None => resource?.into(),
};
let mut conn = Client::new(Method::Select);
conn.execute(router?, Param::new(vec![param])).await
conn.$method(router?, Param::new(vec![param])).await
})
}
};
}
impl<'r, Client> IntoFuture for Select<'r, Client, Value>
where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, R> IntoFuture for Select<'r, Client, Option<R>>
where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_opt}
}
impl<'r, Client, R> IntoFuture for Select<'r, Client, Vec<R>>
@ -62,7 +73,18 @@ where
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_vec}
}
impl<C> Select<'_, C, Value>
where
C: Connection,
{
/// Restricts the records selected to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
self
}
}
impl<C, R> Select<'_, C, Vec<R>>

View file

@ -27,7 +27,7 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut conn = Client::new(Method::Set);
conn.execute(self.router?, Param::new(vec![self.key.into(), self.value?])).await
conn.execute_unit(self.router?, Param::new(vec![self.key.into(), self.value?])).await
})
}
}

View file

@ -111,10 +111,10 @@ async fn api() {
.unwrap();
// create
let _: User = DB.create(USER).await.unwrap();
let _: User = DB.create((USER, "john")).await.unwrap();
let _: User = DB.create(USER).content(User::default()).await.unwrap();
let _: User = DB.create((USER, "john")).content(User::default()).await.unwrap();
let _: Vec<User> = DB.create(USER).await.unwrap();
let _: Option<User> = DB.create((USER, "john")).await.unwrap();
let _: Vec<User> = DB.create(USER).content(User::default()).await.unwrap();
let _: Option<User> = DB.create((USER, "john")).content(User::default()).await.unwrap();
// select
let _: Vec<User> = DB.select(USER).await.unwrap();

View file

@ -25,7 +25,7 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut conn = Client::new(Method::Unset);
conn.execute(self.router?, Param::new(vec![self.key.into()])).await
conn.execute_unit(self.router?, Param::new(vec![self.key.into()])).await
})
}
}

View file

@ -10,6 +10,7 @@ use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::sql::Id;
use crate::sql::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::future::Future;
@ -28,7 +29,7 @@ pub struct Update<'r, C: Connection, R> {
}
macro_rules! into_future {
() => {
($method:ident) => {
fn into_future(self) -> Self::IntoFuture {
let Update {
router,
@ -42,21 +43,31 @@ macro_rules! into_future {
None => resource?.into(),
};
let mut conn = Client::new(Method::Update);
conn.execute(router?, Param::new(vec![param])).await
conn.$method(router?, Param::new(vec![param])).await
})
}
};
}
impl<'r, Client> IntoFuture for Update<'r, Client, Value>
where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {execute_value}
}
impl<'r, Client, R> IntoFuture for Update<'r, Client, Option<R>>
where
Client: Connection,
R: DeserializeOwned,
{
type Output = Result<R>;
type Output = Result<Option<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_opt}
}
impl<'r, Client, R> IntoFuture for Update<'r, Client, Vec<R>>
@ -67,7 +78,18 @@ where
type Output = Result<Vec<R>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
into_future! {}
into_future! {execute_vec}
}
impl<C> Update<'_, C, Value>
where
C: Connection,
{
/// Restricts the records to update to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
self
}
}
impl<C, R> Update<'_, C, Vec<R>>
@ -81,44 +103,7 @@ where
}
}
macro_rules! content {
($this:ident, $data:ident) => {
Content {
router: $this.router,
method: Method::Update,
resource: $this.resource,
range: $this.range,
content: $data,
response_type: PhantomData,
}
};
}
macro_rules! merge {
($this:ident, $data:ident) => {
Merge {
router: $this.router,
resource: $this.resource,
range: $this.range,
content: $data,
response_type: PhantomData,
}
};
}
macro_rules! patch {
($this:ident, $data:ident) => {
Patch {
router: $this.router,
resource: $this.resource,
range: $this.range,
patches: vec![$data],
response_type: PhantomData,
}
};
}
impl<'r, C, R> Update<'r, C, Option<R>>
impl<'r, C, R> Update<'r, C, R>
where
C: Connection,
R: DeserializeOwned,
@ -128,7 +113,14 @@ where
where
D: Serialize,
{
content!(self, data)
Content {
router: self.router,
method: Method::Update,
resource: self.resource,
range: self.range,
content: data,
response_type: PhantomData,
}
}
/// Merges the current document / record data with the specified data
@ -136,38 +128,23 @@ where
where
D: Serialize,
{
merge!(self, data)
Merge {
router: self.router,
resource: self.resource,
range: self.range,
content: data,
response_type: PhantomData,
}
}
/// Patches the current document / record data with the specified JSON Patch data
pub fn patch(self, PatchOp(patch): PatchOp) -> Patch<'r, C, R> {
patch!(self, patch)
}
}
impl<'r, C, R> Update<'r, C, Vec<R>>
where
C: Connection,
R: DeserializeOwned,
{
/// Replaces the current document / record data with the specified data
pub fn content<D>(self, data: D) -> Content<'r, C, D, Vec<R>>
where
D: Serialize,
{
content!(self, data)
}
/// Merges the current document / record data with the specified data
pub fn merge<D>(self, data: D) -> Merge<'r, C, D, Vec<R>>
where
D: Serialize,
{
merge!(self, data)
}
/// Patches the current document / record data with the specified JSON Patch data
pub fn patch(self, PatchOp(patch): PatchOp) -> Patch<'r, C, Vec<R>> {
patch!(self, patch)
Patch {
router: self.router,
resource: self.resource,
range: self.range,
patches: vec![patch],
response_type: PhantomData,
}
}
}

View file

@ -25,7 +25,7 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut conn = Client::new(Method::Use);
conn.execute(self.router?, Param::new(vec![Value::None, self.db.into()]))
conn.execute_unit(self.router?, Param::new(vec![Value::None, self.db.into()]))
.await
})
}

View file

@ -48,7 +48,7 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut conn = Client::new(Method::Use);
conn.execute(self.router?, Param::new(vec![self.ns.into(), self.db.into()])).await
conn.execute_unit(self.router?, Param::new(vec![self.ns.into(), self.db.into()])).await
})
}
}

View file

@ -25,7 +25,10 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async {
let mut conn = Client::new(Method::Version);
let version: String = conn.execute(self.router?, Param::new(Vec::new())).await?;
let version = conn
.execute_value(self.router?, Param::new(Vec::new()))
.await?
.convert_to_string()?;
let semantic = version.trim_start_matches("surrealdb-");
semantic.parse().map_err(|_| Error::InvalidSemanticVersion(semantic.to_string()).into())
})

View file

@ -46,6 +46,71 @@ impl Resource {
}
}
impl From<Table> for Resource {
fn from(table: Table) -> Self {
Self::Table(table)
}
}
impl From<Thing> for Resource {
fn from(thing: Thing) -> Self {
Self::RecordId(thing)
}
}
impl From<Object> for Resource {
fn from(object: Object) -> Self {
Self::Object(object)
}
}
impl From<Array> for Resource {
fn from(array: Array) -> Self {
Self::Array(array)
}
}
impl From<Edges> for Resource {
fn from(edges: Edges) -> Self {
Self::Edges(edges)
}
}
impl From<&str> for Resource {
fn from(s: &str) -> Self {
match sql::thing(s) {
Ok(thing) => Self::RecordId(thing),
Err(_) => Self::Table(s.into()),
}
}
}
impl From<&String> for Resource {
fn from(s: &String) -> Self {
Self::from(s.as_str())
}
}
impl From<String> for Resource {
fn from(s: String) -> Self {
match sql::thing(s.as_str()) {
Ok(thing) => Self::RecordId(thing),
Err(_) => Self::Table(s.into()),
}
}
}
impl<T, I> From<(T, I)> for Resource
where
T: Into<String>,
I: Into<Id>,
{
fn from((table, id): (T, I)) -> Self {
let record_id = (table.into(), id.into());
Self::RecordId(record_id.into())
}
}
impl From<Resource> for Value {
fn from(resource: Resource) -> Self {
match resource {
@ -64,6 +129,12 @@ pub trait IntoResource<Response>: Sized {
fn into_resource(self) -> Result<Resource>;
}
impl IntoResource<Value> for Resource {
fn into_resource(self) -> Result<Resource> {
Ok(self)
}
}
impl<R> IntoResource<Option<R>> for Object {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::Object(self))

View file

@ -49,7 +49,7 @@
//! db.use_ns("namespace").use_db("database").await?;
//!
//! // Create a new person with a random ID
//! let created: Person = db.create("person")
//! let created: Vec<Person> = db.create("person")
//! .content(Person {
//! title: "Founder & CEO".into(),
//! name: Name {
@ -61,7 +61,7 @@
//! .await?;
//!
//! // Create a new person with a specific ID
//! let created: Person = db.create(("person", "jaime"))
//! let created: Option<Person> = db.create(("person", "jaime"))
//! .content(Person {
//! title: "Founder & COO".into(),
//! name: Name {
@ -73,7 +73,7 @@
//! .await?;
//!
//! // Update a person record with a specific ID
//! let updated: Person = db.update(("person", "jaime"))
//! let updated: Option<Person> = db.update(("person", "jaime"))
//! .merge(json!({"marketing": true}))
//! .await?;
//!

View file

@ -13,11 +13,13 @@ mod api_integration {
use surrealdb::opt::auth::Root;
use surrealdb::opt::auth::Scope;
use surrealdb::opt::PatchOp;
use surrealdb::opt::Resource;
use surrealdb::sql::serde::serialize_internal;
use surrealdb::sql::statements::BeginStatement;
use surrealdb::sql::statements::CommitStatement;
use surrealdb::sql::thing;
use surrealdb::sql::Thing;
use surrealdb::sql::Value;
use surrealdb::Error;
use surrealdb::Surreal;
use ulid::Ulid;

View file

@ -9,7 +9,7 @@ async fn export_import() {
let db_name = Ulid::new().to_string();
db.use_ns(NS).use_db(&db_name).await.unwrap();
for i in 0..10 {
let _: RecordId = db
let _: Vec<RecordId> = db
.create("user")
.content(Record {
name: &format!("User {i}"),

View file

@ -97,41 +97,59 @@ async fn query_chaining() {
async fn create_record_no_id() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let _: RecordId = db.create("user").await.unwrap();
let _: Vec<RecordId> = db.create("user").await.unwrap();
let _: Value = db.create(Resource::from("user")).await.unwrap();
}
#[tokio::test]
async fn create_record_with_id() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let _: RecordId = db.create(("user", "john")).await.unwrap();
let _: Option<RecordId> = db.create(("user", "jane")).await.unwrap();
let _: Value = db.create(Resource::from(("user", "john"))).await.unwrap();
let _: Value = db.create(Resource::from("user:doe")).await.unwrap();
}
#[tokio::test]
async fn create_record_no_id_with_content() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let _: RecordId = db
let _: Vec<RecordId> = db
.create("user")
.content(Record {
name: "John Doe",
})
.await
.unwrap();
let _: Value = db
.create(Resource::from("user"))
.content(Record {
name: "John Doe",
})
.await
.unwrap();
}
#[tokio::test]
async fn create_record_with_id_with_content() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let record: RecordId = db
let record: Option<RecordId> = db
.create(("user", "john"))
.content(Record {
name: "John Doe",
})
.await
.unwrap();
assert_eq!(record.id, thing("user:john").unwrap());
assert_eq!(record.unwrap().id, thing("user:john").unwrap());
let value: Value = db
.create(Resource::from("user:jane"))
.content(Record {
name: "Jane Doe",
})
.await
.unwrap();
assert_eq!(value.record(), thing("user:jane").ok());
}
#[tokio::test]
@ -139,9 +157,9 @@ async fn select_table() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let table = "user";
let _: RecordId = db.create(table).await.unwrap();
let _: RecordId = db.create(table).await.unwrap();
let _: RecordId = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Value = db.create(Resource::from(table)).await.unwrap();
let users: Vec<RecordId> = db.select(table).await.unwrap();
assert_eq!(users.len(), 3);
}
@ -151,11 +169,13 @@ async fn select_record_id() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let record_id = ("user", "john");
let _: RecordId = db.create(record_id).await.unwrap();
let _: Option<RecordId> = db.create(record_id).await.unwrap();
let Some(record): Option<RecordId> = db.select(record_id).await.unwrap() else {
panic!("record not found");
};
assert_eq!(record.id, thing("user:john").unwrap());
let value: Value = db.select(Resource::from(record_id)).await.unwrap();
assert_eq!(value.record(), thing("user:john").ok());
}
#[tokio::test]
@ -163,10 +183,10 @@ async fn select_record_ranges() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let table = "user";
let _: RecordId = db.create((table, "amos")).await.unwrap();
let _: RecordId = db.create((table, "jane")).await.unwrap();
let _: RecordId = db.create((table, "john")).await.unwrap();
let _: RecordId = db.create((table, "zoey")).await.unwrap();
let _: Option<RecordId> = db.create((table, "amos")).await.unwrap();
let _: Option<RecordId> = db.create((table, "jane")).await.unwrap();
let _: Option<RecordId> = db.create((table, "john")).await.unwrap();
let _: Value = db.create(Resource::from((table, "zoey"))).await.unwrap();
let convert = |users: Vec<RecordId>| -> Vec<String> {
users
.into_iter()
@ -185,6 +205,10 @@ async fn select_record_ranges() {
assert_eq!(convert(users), vec!["jane"]);
let users: Vec<RecordId> = db.select(table).range("jane"..="john").await.unwrap();
assert_eq!(convert(users), vec!["jane", "john"]);
let Value::Array(array): Value = db.select(Resource::from(table)).range("jane"..="john").await.unwrap() else {
unreachable!();
};
assert_eq!(array.len(), 2);
let users: Vec<RecordId> =
db.select(table).range((Bound::Excluded("jane"), Bound::Included("john"))).await.unwrap();
assert_eq!(convert(users), vec!["john"]);
@ -195,8 +219,9 @@ async fn update_table() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let table = "user";
let _: RecordId = db.create(table).await.unwrap();
let _: RecordId = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Value = db.update(Resource::from(table)).await.unwrap();
let users: Vec<RecordId> = db.update(table).await.unwrap();
assert_eq!(users.len(), 2);
}
@ -206,8 +231,8 @@ async fn update_record_id() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let table = "user";
let _: RecordId = db.create((table, "john")).await.unwrap();
let _: RecordId = db.create((table, "jane")).await.unwrap();
let _: Option<RecordId> = db.create((table, "john")).await.unwrap();
let _: Option<RecordId> = db.create((table, "jane")).await.unwrap();
let users: Vec<RecordId> = db.update(table).await.unwrap();
assert_eq!(users.len(), 2);
}
@ -324,27 +349,27 @@ async fn update_record_id_with_content() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let record_id = ("user", "john");
let user: RecordName = db
let user: Option<RecordName> = db
.create(record_id)
.content(Record {
name: "Jane Doe",
})
.await
.unwrap();
assert_eq!(user.name, "Jane Doe");
let user: RecordName = db
assert_eq!(user.unwrap().name, "Jane Doe");
let user: Option<RecordName> = db
.update(record_id)
.content(Record {
name: "John Doe",
})
.await
.unwrap();
assert_eq!(user.name, "John Doe");
let user: RecordName = db
assert_eq!(user.unwrap().name, "John Doe");
let user: Option<RecordName> = db
.select(record_id)
.await
.unwrap();
assert_eq!(user.name, "John Doe");
assert_eq!(user.unwrap().name, "John Doe");
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
@ -367,7 +392,7 @@ async fn merge_record_id() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let record_id = ("person", "jaime");
let mut jaime: Person = db
let mut jaime: Option<Person> = db
.create(record_id)
.content(Person {
id: None,
@ -380,15 +405,15 @@ async fn merge_record_id() {
})
.await
.unwrap();
assert_eq!(jaime.id.unwrap(), thing("person:jaime").unwrap());
assert_eq!(jaime.unwrap().id.unwrap(), thing("person:jaime").unwrap());
jaime = db
.update(record_id)
.merge(json!({ "marketing": true }))
.await
.unwrap();
assert!(jaime.marketing);
assert!(jaime.as_ref().unwrap().marketing);
jaime = db.select(record_id).await.unwrap();
assert_eq!(jaime, Person {
assert_eq!(jaime.unwrap(), Person {
id: Some(thing("person:jaime").unwrap()),
title: "Founder & COO".into(),
name: Name {
@ -435,9 +460,9 @@ async fn delete_table() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let table = "user";
let _: RecordId = db.create(table).await.unwrap();
let _: RecordId = db.create(table).await.unwrap();
let _: RecordId = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let users: Vec<RecordId> = db.select(table).await.unwrap();
assert_eq!(users.len(), 3);
let users: Vec<RecordId> = db.delete(table).await.unwrap();
@ -451,12 +476,17 @@ async fn delete_record_id() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let record_id = ("user", "john");
let _: RecordId = db.create(record_id).await.unwrap();
let _: RecordId = db.select(record_id).await.unwrap();
let _: Option<RecordId> = db.create(record_id).await.unwrap();
let _: Option<RecordId> = db.select(record_id).await.unwrap();
let john: Option<RecordId> = db.delete(record_id).await.unwrap();
assert!(john.is_some());
let john: Option<RecordId> = db.select(record_id).await.unwrap();
assert!(john.is_none());
// non-existing user
let jane: Option<RecordId> = db.delete(("user", "jane")).await.unwrap();
assert!(jane.is_none());
let value = db.delete(Resource::from(("user", "jane"))).await.unwrap();
assert_eq!(value, Value::None);
}
#[tokio::test]
@ -534,4 +564,7 @@ async fn return_bool() {
panic!("record not found");
};
assert!(boolean);
let mut response = db.query("RETURN false").await.unwrap();
let value: Value = response.take(0).unwrap();
assert_eq!(value, vec![Value::False].into());
}