Add run to sdk (#4305)

Co-authored-by: Micha de Vries <mt.dev@hotmail.com>
This commit is contained in:
Raphael Darley 2024-08-20 03:50:19 -07:00 committed by GitHub
parent e18cd17ca6
commit b1e9af5d4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 339 additions and 0 deletions

View file

@ -6,6 +6,7 @@ use revision::Revisioned;
use serde::{ser::SerializeMap as _, Serialize};
use std::path::PathBuf;
use std::{collections::BTreeMap, io::Read};
use surrealdb_core::sql::Array;
use surrealdb_core::{
dbs::Notification,
sql::{Object, Query, Value},
@ -99,6 +100,11 @@ pub(crate) enum Command {
Kill {
uuid: Uuid,
},
Run {
name: String,
version: Option<String>,
args: Array,
},
}
impl Command {
@ -318,6 +324,17 @@ impl Command {
method: "kill".into(),
params: Some(vec![Value::from(uuid)].into()),
},
Command::Run {
name,
version,
args,
} => RouterRequest {
id,
method: "run".into(),
params: Some(
vec![Value::from(name), Value::from(version), Value::Array(args)].into(),
),
},
};
Some(res)
}

View file

@ -59,6 +59,9 @@ use surrealdb_core::{
use crate::api::err::Error;
#[cfg(not(target_arch = "wasm32"))]
use std::path::PathBuf;
use surrealdb_core::sql::Function;
#[cfg(feature = "ml")]
use surrealdb_core::sql::Model;
#[cfg(not(target_arch = "wasm32"))]
use tokio::{
fs::OpenOptions,
@ -981,5 +984,34 @@ async fn router(
let value = kill_live_query(kvs, uuid.into(), session, vars.clone()).await?;
Ok(DbResponse::Other(value))
}
Command::Run {
name,
version: _version,
args,
} => {
let func: Value = match &name[0..4] {
"fn::" => Function::Custom(name.chars().skip(4).collect(), args.0).into(),
// should return error, but can't on wasm
#[cfg(feature = "ml")]
"ml::" => {
let mut tmp = Model::default();
tmp.name = name.chars().skip(4).collect();
tmp.args = args.0;
tmp.version = _version
.ok_or(Error::Query("ML functions must have a version".to_string()))?;
tmp
}
.into(),
_ => Function::Normal(name, args.0).into(),
};
let stmt = Statement::Value(func);
let response = kvs.process(stmt.into(), &*session, Some(vars.clone())).await?;
let value = take(true, response).await?;
Ok(DbResponse::Other(value))
}
}
}

View file

@ -12,6 +12,7 @@ use crate::api::Surreal;
use crate::opt::IntoExportDestination;
use crate::opt::WaitFor;
use crate::sql::to_value;
use run::IntoArgs;
use serde::Serialize;
use std::borrow::Cow;
use std::marker::PhantomData;
@ -38,6 +39,7 @@ mod insert;
mod invalidate;
mod merge;
mod patch;
mod run;
mod select;
mod set;
mod signin;
@ -75,6 +77,8 @@ pub use merge::Merge;
pub use patch::Patch;
pub use query::Query;
pub use query::QueryStream;
use run::IntoFn;
pub use run::Run;
pub use select::Select;
use serde_content::Serializer;
pub use set::Set;
@ -1222,6 +1226,39 @@ where
}
}
/// Runs a function
///
/// # Examples
///
/// ```no_run
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
/// # let db = surrealdb::engine::any::connect("mem://").await?;
/// // specify no args with an empty tuple, vec, or slice
/// let foo = db.run("fn::foo", ()).await?; // fn::foo()
/// // a single value will be turned into one arguement unless it is a tuple or vec
/// let bar = db.run("fn::bar", 42).await?; // fn::bar(42)
/// // to specify a single arguement, which is an array turn it into a value, or wrap in a singleton tuple
/// let count = db.run("count", Value::from(vec![1,2,3])).await?;
/// let count = db.run("count", (vec![1,2,3],)).await?;
/// // specify multiple args with either a tuple or vec
/// let two = db.run("math::log", (100, 10)).await?; // math::log(100, 10)
/// let two = db.run("math::log", [100, 10]).await?; // math::log(100, 10)
///
/// # Ok(())
/// # }
/// ```
///
pub fn run(&self, name: impl IntoFn, args: impl IntoArgs) -> Run<C> {
let (name, version) = name.into_fn();
Run {
client: Cow::Borrowed(self),
name,
version,
args: args.into_args(),
}
}
/// Checks whether the server is healthy or not
///
/// # Examples

207
lib/src/api/method/run.rs Normal file
View file

@ -0,0 +1,207 @@
use crate::api::conn::Command;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Array;
use crate::sql::Value;
use crate::Surreal;
use std::borrow::Cow;
use std::future::Future;
use std::future::IntoFuture;
use std::pin::Pin;
/// A run future
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Run<'r, C: Connection> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) name: String,
pub(super) version: Option<String>,
pub(super) args: Array,
}
impl<C> Run<'_, C>
where
C: Connection,
{
/// Converts to an owned type which can easily be moved to a different thread
pub fn into_owned(self) -> Run<'static, C> {
Run {
client: Cow::Owned(self.client.into_owned()),
..self
}
}
}
impl<'r, Client> IntoFuture for Run<'r, Client>
where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
fn into_future(self) -> Self::IntoFuture {
let Run {
client,
name,
version,
args,
} = self;
Box::pin(async move {
let router = client.router.extract()?;
router
.execute_value(Command::Run {
name,
version,
args,
})
.await
})
}
}
pub trait IntoArgs {
fn into_args(self) -> Array;
}
impl IntoArgs for Array {
fn into_args(self) -> Array {
self
}
}
impl IntoArgs for Value {
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self];
Array::from(arr)
}
}
impl<T> IntoArgs for Vec<T>
where
T: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.into_iter().map(Into::into).collect();
Array::from(arr)
}
}
impl<T, const N: usize> IntoArgs for [T; N]
where
T: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.into_iter().map(Into::into).collect();
Array::from(arr)
}
}
impl<T, const N: usize> IntoArgs for &[T; N]
where
T: Into<Value> + Clone,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.iter().cloned().map(Into::into).collect();
Array::from(arr)
}
}
impl<T> IntoArgs for &[T]
where
T: Into<Value> + Clone,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.iter().cloned().map(Into::into).collect();
Array::from(arr)
}
}
impl IntoArgs for () {
fn into_args(self) -> Array {
Vec::<Value>::new().into()
}
}
impl<T0> IntoArgs for (T0,)
where
T0: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self.0.into()];
Array::from(arr)
}
}
impl<T0, T1> IntoArgs for (T0, T1)
where
T0: Into<Value>,
T1: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self.0.into(), self.1.into()];
Array::from(arr)
}
}
impl<T0, T1, T2> IntoArgs for (T0, T1, T2)
where
T0: Into<Value>,
T1: Into<Value>,
T2: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self.0.into(), self.1.into(), self.2.into()];
Array::from(arr)
}
}
macro_rules! into_impl {
($type:ty) => {
impl IntoArgs for $type {
fn into_args(self) -> Array {
let val: Value = self.into();
Array::from(val)
}
}
};
}
into_impl!(i8);
into_impl!(i16);
into_impl!(i32);
into_impl!(i64);
into_impl!(i128);
into_impl!(u8);
into_impl!(u16);
into_impl!(u32);
into_impl!(u64);
into_impl!(u128);
into_impl!(usize);
into_impl!(isize);
into_impl!(f32);
into_impl!(f64);
into_impl!(String);
into_impl!(&str);
pub trait IntoFn {
fn into_fn(self) -> (String, Option<String>);
}
impl IntoFn for String {
fn into_fn(self) -> (String, Option<String>) {
(self, None)
}
}
impl IntoFn for &str {
fn into_fn(self) -> (String, Option<String>) {
(self.to_owned(), None)
}
}
impl<S0, S1> IntoFn for (S0, S1)
where
S0: Into<String>,
S1: Into<String>,
{
fn into_fn(self) -> (String, Option<String>) {
(self.0.into(), Some(self.1.into()))
}
}

View file

@ -21,6 +21,7 @@ use protocol::Client;
use protocol::Test;
use semver::Version;
use std::ops::Bound;
use surrealdb_core::sql::Value;
use types::User;
use types::USER;
@ -168,6 +169,9 @@ async fn api() {
// version
let _: Version = DB.version().await.unwrap();
// run
let _: Value = DB.run("foo", ()).await.unwrap();
}
fn assert_send_sync(_: impl Send + Sync) {}

View file

@ -101,6 +101,9 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
}
_ => unreachable!(),
},
Command::Run {
..
} => Ok(DbResponse::Other(Value::None)),
Command::ExportMl {
..
}

View file

@ -1356,3 +1356,42 @@ async fn return_bool() {
let value: Value = response.take(0).unwrap();
assert_eq!(value, Value::Bool(false));
}
#[test_log::test(tokio::test)]
async fn run() {
let (permit, db) = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let sql = "
DEFINE FUNCTION fn::foo() {
RETURN 42;
};
DEFINE FUNCTION fn::bar($val: any) {
CREATE foo:1 set val = $val;
};
DEFINE FUNCTION fn::baz() {
RETURN SELECT VALUE val FROM ONLY foo:1;
};
";
let _ = db.query(sql).await;
let tmp = db.run("fn::foo", ()).await.unwrap();
assert_eq!(tmp, Value::from(42));
let tmp = db.run("fn::foo", 7).await.unwrap_err();
println!("fn::foo res: {tmp}");
assert!(tmp.to_string().contains("The function expects 0 arguments."));
let tmp = db.run("fn::idnotexist", ()).await.unwrap_err();
println!("fn::idontexist res: {tmp}");
assert!(tmp.to_string().contains("The function 'fn::idnotexist' does not exist"));
let tmp = db.run("count", Value::from(vec![1, 2, 3])).await.unwrap();
assert_eq!(tmp, Value::from(3));
let tmp = db.run("fn::bar", 7).await.unwrap();
assert_eq!(tmp, Value::None);
let tmp = db.run("fn::baz", ()).await.unwrap();
assert_eq!(tmp, Value::from(7));
}