Add run to sdk (#4305)
Co-authored-by: Micha de Vries <mt.dev@hotmail.com>
This commit is contained in:
parent
e18cd17ca6
commit
b1e9af5d4e
7 changed files with 339 additions and 0 deletions
|
@ -6,6 +6,7 @@ use revision::Revisioned;
|
||||||
use serde::{ser::SerializeMap as _, Serialize};
|
use serde::{ser::SerializeMap as _, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{collections::BTreeMap, io::Read};
|
use std::{collections::BTreeMap, io::Read};
|
||||||
|
use surrealdb_core::sql::Array;
|
||||||
use surrealdb_core::{
|
use surrealdb_core::{
|
||||||
dbs::Notification,
|
dbs::Notification,
|
||||||
sql::{Object, Query, Value},
|
sql::{Object, Query, Value},
|
||||||
|
@ -99,6 +100,11 @@ pub(crate) enum Command {
|
||||||
Kill {
|
Kill {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
},
|
},
|
||||||
|
Run {
|
||||||
|
name: String,
|
||||||
|
version: Option<String>,
|
||||||
|
args: Array,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
|
@ -318,6 +324,17 @@ impl Command {
|
||||||
method: "kill".into(),
|
method: "kill".into(),
|
||||||
params: Some(vec![Value::from(uuid)].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)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,9 @@ use surrealdb_core::{
|
||||||
use crate::api::err::Error;
|
use crate::api::err::Error;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use surrealdb_core::sql::Function;
|
||||||
|
#[cfg(feature = "ml")]
|
||||||
|
use surrealdb_core::sql::Model;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::OpenOptions,
|
fs::OpenOptions,
|
||||||
|
@ -981,5 +984,34 @@ async fn router(
|
||||||
let value = kill_live_query(kvs, uuid.into(), session, vars.clone()).await?;
|
let value = kill_live_query(kvs, uuid.into(), session, vars.clone()).await?;
|
||||||
Ok(DbResponse::Other(value))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::api::Surreal;
|
||||||
use crate::opt::IntoExportDestination;
|
use crate::opt::IntoExportDestination;
|
||||||
use crate::opt::WaitFor;
|
use crate::opt::WaitFor;
|
||||||
use crate::sql::to_value;
|
use crate::sql::to_value;
|
||||||
|
use run::IntoArgs;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
@ -38,6 +39,7 @@ mod insert;
|
||||||
mod invalidate;
|
mod invalidate;
|
||||||
mod merge;
|
mod merge;
|
||||||
mod patch;
|
mod patch;
|
||||||
|
mod run;
|
||||||
mod select;
|
mod select;
|
||||||
mod set;
|
mod set;
|
||||||
mod signin;
|
mod signin;
|
||||||
|
@ -75,6 +77,8 @@ pub use merge::Merge;
|
||||||
pub use patch::Patch;
|
pub use patch::Patch;
|
||||||
pub use query::Query;
|
pub use query::Query;
|
||||||
pub use query::QueryStream;
|
pub use query::QueryStream;
|
||||||
|
use run::IntoFn;
|
||||||
|
pub use run::Run;
|
||||||
pub use select::Select;
|
pub use select::Select;
|
||||||
use serde_content::Serializer;
|
use serde_content::Serializer;
|
||||||
pub use set::Set;
|
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
|
/// Checks whether the server is healthy or not
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
207
lib/src/api/method/run.rs
Normal file
207
lib/src/api/method/run.rs
Normal 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()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ use protocol::Client;
|
||||||
use protocol::Test;
|
use protocol::Test;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
|
use surrealdb_core::sql::Value;
|
||||||
use types::User;
|
use types::User;
|
||||||
use types::USER;
|
use types::USER;
|
||||||
|
|
||||||
|
@ -168,6 +169,9 @@ async fn api() {
|
||||||
|
|
||||||
// version
|
// version
|
||||||
let _: Version = DB.version().await.unwrap();
|
let _: Version = DB.version().await.unwrap();
|
||||||
|
|
||||||
|
// run
|
||||||
|
let _: Value = DB.run("foo", ()).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_send_sync(_: impl Send + Sync) {}
|
fn assert_send_sync(_: impl Send + Sync) {}
|
||||||
|
|
|
@ -101,6 +101,9 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
|
Command::Run {
|
||||||
|
..
|
||||||
|
} => Ok(DbResponse::Other(Value::None)),
|
||||||
Command::ExportMl {
|
Command::ExportMl {
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
|
|
|
@ -1356,3 +1356,42 @@ async fn return_bool() {
|
||||||
let value: Value = response.take(0).unwrap();
|
let value: Value = response.take(0).unwrap();
|
||||||
assert_eq!(value, Value::Bool(false));
|
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));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue