use std::sync::Arc;
use anyhow::Result;
use surrealdb_types::{HashMap, object};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::catalog::providers::{CatalogProvider, NamespaceProvider, RootProvider};
use crate::ctx::CancelHandle;
use crate::dbs::capabilities::{ExperimentalTarget, MethodTarget};
use crate::dbs::{QueryResult, QueryType, Session};
use crate::iam::token::Token;
use crate::kvs::{Datastore, LockType, TransactionType};
use crate::observe::{
AuthAction, AuthEvent, AuthEventSafe, AuthScope, Outcome, RpcEvent, RpcEventSafe,
TenantIdentity,
};
use crate::rpc::args::extract_args;
use crate::rpc::{
DbResult, Method, bad_lq_config, invalid_params, method_not_allowed, method_not_found,
session_exists, session_expired, session_not_found, types_error_from_anyhow,
};
use crate::sql::statements::live::LiveFields;
use crate::sql::{
Ast, CreateStatement, Data as SqlData, DeleteStatement, Expr, Fields, Function, FunctionCall,
InsertStatement, KillStatement, Literal, LiveStatement, Model, Output, RelateStatement,
SelectStatement, TopLevelExpr, UpdateStatement, UpsertStatement,
};
use crate::types::{
PublicArray, PublicRecordIdKey, PublicUuid, PublicValue, PublicVariables, SurrealValue,
};
fn value_to_table(value: PublicValue) -> Expr {
match value {
PublicValue::String(s) => Expr::Table(crate::val::TableName::new(s)),
x => Expr::from_public_value(x),
}
}
fn singular(value: &PublicValue) -> bool {
match value {
PublicValue::Object(_) => true,
PublicValue::RecordId(t) => !matches!(t.key, PublicRecordIdKey::Range(_)),
_ => false,
}
}
struct AuthPrincipalSnapshot {
id: String,
level: crate::iam::Level,
}
impl AuthPrincipalSnapshot {
fn capture(session: &Session) -> Self {
Self {
id: session.au.id().to_string(),
level: session.au.level().clone(),
}
}
fn differs_from(&self, session: &Session) -> bool {
session.au.id() != self.id || session.au.level() != &self.level
}
}
const fn method_to_auth_action(method: Method) -> Option<AuthAction> {
match method {
Method::Signup => Some(AuthAction::Signup),
Method::Signin => Some(AuthAction::Signin),
Method::Authenticate => Some(AuthAction::Authenticate),
Method::Refresh => Some(AuthAction::Refresh),
Method::Invalidate => Some(AuthAction::Invalidate),
Method::Revoke => Some(AuthAction::Revoke),
_ => None,
}
}
#[expect(async_fn_in_trait)]
pub trait RpcProtocol {
fn kvs(&self) -> &Datastore;
fn kvs_arc(&self) -> Arc<Datastore>;
fn version_data(&self) -> DbResult;
fn cancel_handle(&self) -> Option<CancelHandle> {
None
}
fn session_map(&self) -> &HashMap<Uuid, Arc<RwLock<Session>>>;
async fn attach(&self, session_id: Uuid) -> Result<DbResult, surrealdb_types::Error> {
if self.session_map().contains_key(&session_id) {
return Err(session_exists(session_id));
}
let mut session = Session::default().with_rt(Self::LQ_SUPPORT);
session.id = Some(session_id);
self.session_map().insert(session_id, Arc::new(RwLock::new(session)));
Ok(DbResult::Other(PublicValue::None))
}
async fn detach(&self, session_id: Uuid) -> Result<DbResult, surrealdb_types::Error> {
self.del_session(&session_id).await;
Ok(DbResult::Other(PublicValue::None))
}
fn get_session(&self, id: &Uuid) -> Result<Arc<RwLock<Session>>, surrealdb_types::Error> {
match self.session_map().get(id) {
Some(session) => Ok(session),
None => Err(session_not_found(*id)),
}
}
fn set_session(&self, id: Uuid, session: Arc<RwLock<Session>>) {
self.session_map().insert(id, session);
}
async fn del_session(&self, id: &Uuid) {
self.session_map().remove(id);
self.cleanup_lqs(id).await;
}
async fn sessions(&self) -> Result<DbResult, surrealdb_types::Error> {
let array = self
.session_map()
.to_vec()
.into_iter()
.map(|(key, _)| PublicValue::Uuid(PublicUuid::from(key)))
.collect();
Ok(DbResult::Other(PublicValue::Array(array)))
}
async fn get_tx(
&self,
_id: Uuid,
) -> Result<Arc<crate::kvs::Transaction>, surrealdb_types::Error> {
Err(method_not_allowed(Method::Unknown.to_string()))
}
async fn set_tx(
&self,
_id: Uuid,
_tx: Arc<crate::kvs::Transaction>,
) -> Result<(), surrealdb_types::Error> {
Err(method_not_found(Method::Unknown.to_string()))
}
const LQ_SUPPORT: bool = false;
fn handle_live(
&self,
_lqid: &Uuid,
_session_id: Uuid,
_namespace: Option<String>,
_database: Option<String>,
) -> impl std::future::Future<Output = ()> + Send {
async { unimplemented!("handle_live function must be implemented if LQ_SUPPORT = true") }
}
fn handle_kill(&self, _lqid: &Uuid) -> impl std::future::Future<Output = ()> + Send {
async { unimplemented!("handle_kill function must be implemented if LQ_SUPPORT = true") }
}
fn cleanup_lqs(&self, session_id: &Uuid) -> impl std::future::Future<Output = ()> + Send;
fn cleanup_all_lqs(&self) -> impl std::future::Future<Output = ()> + Send;
#[tracing::instrument(
level = "debug",
target = "surrealdb::core::rpc",
name = "rpc.execute",
skip_all,
fields(rpc.method = method.to_str(), rpc.session = %session)
)]
async fn execute(
&self,
txn: Option<Uuid>,
session: Uuid,
client_session: Option<Uuid>,
method: Method,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let start = web_time::Instant::now();
let result: Result<DbResult, surrealdb_types::Error> = async {
if !self.kvs().allows_rpc_method(&MethodTarget {
method,
}) {
warn!("Capabilities denied RPC method call attempt, target: '{method}'");
return Err(method_not_allowed(method.to_string()));
}
match method {
Method::Ping => Ok(DbResult::Other(PublicValue::None)),
Method::Info => self.info(txn, session).await,
Method::Use => self.yuse(session, params).await,
Method::Signup => self.signup(session, params).await,
Method::Signin => self.signin(session, params).await,
Method::Authenticate => self.authenticate(session, params).await,
Method::Refresh => self.refresh(session, params).await,
Method::Invalidate => self.invalidate(session).await,
Method::Revoke => self.revoke(params).await,
Method::Reset => self.reset(session).await,
Method::Kill => self.kill(txn, session, params).await,
Method::Live => self.live(txn, session, params).await,
Method::Set => self.set(session, params).await,
Method::Unset => self.unset(session, params).await,
Method::Query => self.query(txn, session, params).await,
Method::Gql => self.gql(txn, session, params).await,
Method::Graphql => self.graphql(txn, session, params).await,
Method::Version => self.version(txn, params).await,
Method::Begin => self.begin(txn, session).await,
Method::Commit => self.commit(txn, session, params).await,
Method::Cancel => self.cancel(txn, session, params).await,
Method::Sessions => self.sessions().await,
Method::Attach => match client_session {
Some(id) => self.attach(id).await,
None => Err(invalid_params("Expected a session ID")),
},
Method::Detach => match client_session {
Some(id) => self.detach(id).await,
None => Err(invalid_params("Expected a session ID")),
},
Method::Select => self.select(txn, session, params).await,
Method::Insert => self.insert(txn, session, params).await,
Method::Create => self.create(txn, session, params).await,
Method::Upsert => self.upsert(txn, session, params).await,
Method::Update => self.update(txn, session, params).await,
Method::Merge => self.merge(txn, session, params).await,
Method::Patch => self.patch(txn, session, params).await,
Method::Delete => self.delete(txn, session, params).await,
Method::Relate => self.relate(txn, session, params).await,
Method::Run => self.run(txn, session, params).await,
Method::InsertRelation => self.insert_relation(txn, session, params).await,
_ => Err(method_not_found(method.to_string())),
}
}
.await;
let outcome = Outcome::from(&result);
let (identity, scope) = match self.get_session(&session) {
Ok(session_lock) => {
let s = session_lock.read().await;
let scope = AuthScope::from(s.au.level());
(TenantIdentity::from_session(&s), scope)
}
Err(_) => (TenantIdentity::default(), AuthScope::None),
};
let error_class =
result.as_ref().err().map(crate::observe::error_class::classify_types_error);
let observer = self.kvs().observer();
observer.on_rpc_complete(&RpcEvent {
safe: RpcEventSafe {
method,
outcome,
duration: start.elapsed(),
error_class,
},
ctx: identity.to_rpc_ctx(),
});
if let Some(action) = method_to_auth_action(method) {
observer.on_auth_event(&AuthEvent {
safe: AuthEventSafe {
action,
scope,
outcome,
error_class,
},
ctx: identity.to_auth_ctx(),
});
}
result
}
async fn yuse(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
{
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Use.to_string()));
}
}
let (ns, db) = extract_args::<(PublicValue, PublicValue)>(params.into_vec())
.ok_or(invalid_params("Expected (ns, db)".to_string()))?;
let mut session = session_lock.write().await;
if ns.is_none() && db.is_none() {
if session.ns.is_none() {
let kvs = self.kvs();
let tx = kvs
.transaction(TransactionType::Write, LockType::Optimistic)
.await
.map_err(types_error_from_anyhow)?;
let (ns, db) = if let Some(x) = match tx.get_default_config().await {
Err(e) => {
let _ = tx.cancel().await;
return Err(types_error_from_anyhow(e));
}
Ok(v) => v,
} {
(x.namespace.clone(), x.database.clone())
} else {
(None, None)
};
if let Some(ns) = ns {
match tx.get_or_add_ns(None, &ns).await {
Err(e) => {
let _ = tx.cancel().await;
return Err(types_error_from_anyhow(e));
}
Ok(v) => v,
};
if let Some(db) = db {
match tx.ensure_ns_db(None, &ns, &db).await {
Err(e) => {
let _ = tx.cancel().await;
return Err(types_error_from_anyhow(e));
}
Ok(v) => v,
};
session.db = Some(db);
}
session.ns = Some(ns);
}
if let Err(e) = tx.commit().await {
let _ = tx.cancel().await;
return Err(types_error_from_anyhow(e));
}
}
} else {
match ns {
PublicValue::None => (),
PublicValue::Null => session.ns = None,
PublicValue::String(ns) => {
let kvs = self.kvs();
let tx = kvs
.transaction(TransactionType::Write, LockType::Optimistic)
.await
.map_err(types_error_from_anyhow)?;
let create = kvs
.should_materialize_ns_on_use(&tx, session.au.as_ref(), &ns)
.await
.map_err(types_error_from_anyhow)?;
if create {
run!(tx, tx.get_or_add_ns(None, &ns).await)
.map_err(types_error_from_anyhow)?;
} else {
let _ = tx.cancel().await;
}
session.ns = Some(ns)
}
unexpected => {
return Err(invalid_params(format!(
"Expected ns to be string, got {unexpected:?}"
)));
}
}
match db {
PublicValue::None => (),
PublicValue::Null => session.db = None,
PublicValue::String(db) => {
let Some(ns) = session.ns.clone() else {
return Err(invalid_params(
"Cannot set database without first selecting a namespace".to_string(),
));
};
let kvs = self.kvs();
let tx = kvs
.transaction(TransactionType::Write, LockType::Optimistic)
.await
.map_err(types_error_from_anyhow)?;
let create = kvs
.should_materialize_db_on_use(&tx, session.au.as_ref(), &ns, &db)
.await
.map_err(types_error_from_anyhow)?;
if create {
run!(tx, tx.ensure_ns_db(None, &ns, &db).await)
.map_err(types_error_from_anyhow)?;
} else {
let _ = tx.cancel().await;
}
session.db = Some(db)
}
unexpected => {
return Err(invalid_params(format!(
"Expected db to be string, got {unexpected:?}"
)));
}
}
}
if session.ns.is_none() && session.db.is_some() {
session.db = None;
}
trace!(
"USE response: session_id={:?}, ns={:?}, db={:?}",
session_id, session.ns, session.db
);
let value = PublicValue::from_t(object! {
namespace: session.ns.clone(),
database: session.db.clone(),
});
Ok(DbResult::Other(value))
}
#[tracing::instrument(
level = "debug",
target = "surrealdb::core::rpc",
name = "rpc.signup",
skip_all,
fields(rpc.session = %session_id)
)]
async fn signup(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let Some(PublicValue::Object(params)) = extract_args(params.into_vec()) else {
return Err(invalid_params("Expected (params:object)".to_string()));
};
let session_lock = self.get_session(&session_id)?;
let mut session = session_lock.write().await;
let snapshot = AuthPrincipalSnapshot::capture(&session);
let out: Result<PublicValue> =
crate::iam::signup::signup(self.kvs(), &mut session, params.into())
.await
.map(SurrealValue::into_value);
let principal_changed = snapshot.differs_from(&session);
drop(session);
if principal_changed {
self.cleanup_lqs(&session_id).await;
}
out.map(DbResult::Other).map_err(types_error_from_anyhow)
}
#[tracing::instrument(
level = "debug",
target = "surrealdb::core::rpc",
name = "rpc.signin",
skip_all,
fields(rpc.session = %session_id)
)]
async fn signin(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let Some(PublicValue::Object(params)) = extract_args(params.into_vec()) else {
return Err(invalid_params("Expected (params:object)".to_string()));
};
let session_lock = self.get_session(&session_id)?;
let mut session = session_lock.write().await;
let snapshot = AuthPrincipalSnapshot::capture(&session);
let out: Result<PublicValue> =
crate::iam::signin::signin(self.kvs(), &mut session, params.into())
.await
.map(SurrealValue::into_value);
let principal_changed = snapshot.differs_from(&session);
drop(session);
if principal_changed {
self.cleanup_lqs(&session_id).await;
}
out.map(DbResult::Other).map_err(types_error_from_anyhow)
}
#[tracing::instrument(
level = "debug",
target = "surrealdb::core::rpc",
name = "rpc.authenticate",
skip_all,
fields(rpc.session = %session_id)
)]
async fn authenticate(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let Some(PublicValue::String(token)) = extract_args(params.into_vec()) else {
return Err(invalid_params("Expected (token:string)".to_string()));
};
let session_lock = self.get_session(&session_id)?;
let mut session = session_lock.write().await;
let snapshot = AuthPrincipalSnapshot::capture(&session);
trace!(
"Authenticate RPC: session_id={:?}, before: ns={:?}, db={:?}",
session_id, session.ns, session.db
);
let out: Result<PublicValue> =
crate::iam::verify::token(self.kvs(), &mut session, token.as_str())
.await
.map(|_| PublicValue::None);
trace!(
"Authenticate RPC: session_id={:?}, after: ns={:?}, db={:?}",
session_id, session.ns, session.db
);
let principal_changed = snapshot.differs_from(&session);
drop(session);
if principal_changed {
self.cleanup_lqs(&session_id).await;
}
out.map(DbResult::Other).map_err(types_error_from_anyhow)
}
async fn refresh(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let unexpected = || invalid_params("Expected (token:Token)".to_string());
let Some(value) = extract_args(params.into_vec()) else {
return Err(unexpected());
};
let Ok(token) = Token::from_value(value) else {
return Err(unexpected());
};
let session_lock = self.get_session(&session_id)?;
let mut session = session_lock.write().await;
let snapshot = AuthPrincipalSnapshot::capture(&session);
let out: Result<PublicValue> =
token.refresh(self.kvs(), &mut session).await.map(Token::into_value);
let principal_changed = snapshot.differs_from(&session);
drop(session);
if principal_changed {
self.cleanup_lqs(&session_id).await;
}
out.map(DbResult::Other).map_err(types_error_from_anyhow)
}
async fn invalidate(&self, session_id: Uuid) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let mut session = session_lock.write().await;
crate::iam::clear::clear(&mut session).map_err(types_error_from_anyhow)?;
self.cleanup_lqs(&session_id).await;
Ok(DbResult::Other(PublicValue::None))
}
async fn revoke(&self, params: PublicArray) -> Result<DbResult, surrealdb_types::Error> {
let unexpected = || invalid_params("Expected (token:Token)".to_string());
let Some(value) = extract_args(params.into_vec()) else {
return Err(unexpected());
};
let Ok(token) = Token::from_value(value) else {
return Err(unexpected());
};
token.revoke_refresh_token(self.kvs()).await.map_err(types_error_from_anyhow)?;
Ok(DbResult::Other(PublicValue::None))
}
async fn reset(&self, session_id: Uuid) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let mut session = session_lock.write().await;
crate::iam::reset::reset(&mut session);
self.cleanup_lqs(&session_id).await;
Ok(DbResult::Other(PublicValue::None))
}
async fn info(
&self,
_txn: Option<Uuid>,
session_id: Uuid,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
let vars = Some(session.variables.clone());
let mut res = self.kvs().execute("SELECT * FROM $auth", &session, vars).await?;
let result = res.remove(0).result?;
let first = result.first().unwrap_or_default();
Ok(DbResult::Other(first))
}
async fn set(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
{
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Set.to_string()));
}
}
let Some((PublicValue::String(key), val)) =
extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
else {
return Err(invalid_params("Expected (key:string, value:Value)".to_string()));
};
let mut session = session_lock.write().await;
if session.expired() {
return Err(session_expired());
}
match val {
None | Some(PublicValue::None) => session.variables.remove(key.as_str()),
Some(val) => {
crate::rpc::check_protected_param(&key)?;
session.variables.insert(key, val)
}
}
Ok(DbResult::Other(PublicValue::Null))
}
async fn unset(
&self,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
{
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Unset.to_string()));
}
}
let Some(PublicValue::String(key)) = extract_args(params.into_vec()) else {
return Err(invalid_params("Expected (key)".to_string()));
};
let mut session = session_lock.write().await;
session.variables.remove(key.as_str());
Ok(DbResult::Other(PublicValue::Null))
}
async fn kill(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Kill.to_string()));
}
let (id,) = extract_args::<(PublicValue,)>(params.into_vec())
.ok_or(invalid_params("Expected (id)".to_string()))?;
let ast = Ast {
expressions: vec![TopLevelExpr::Kill(KillStatement {
id: Expr::from_public_value(id),
})],
};
let vars = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), vars)
.await
.map_err(types_error_from_anyhow)?;
Ok(DbResult::Other(res.remove(0).result?))
}
async fn live(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Live.to_string()));
}
let (what, diff) = extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (what, diff)".to_string()))?;
let what = match what {
PublicValue::String(x) => Expr::Table(crate::val::TableName::new(x)),
x => Expr::from_public_value(x),
};
let fields = if diff.unwrap_or_default().is_true() {
LiveFields::Diff
} else {
LiveFields::Select(Fields::all())
};
let sql = LiveStatement {
fields,
what,
cond: None,
fetch: None,
};
let ast = Ast {
expressions: vec![TopLevelExpr::Live(Box::new(sql))],
};
let vars = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), vars)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn select(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Select.to_string()));
}
let (what,) = extract_args::<(PublicValue,)>(params.into_vec())
.ok_or(invalid_params("Expected (what:Value)".to_string()))?;
let only = match what {
PublicValue::RecordId(ref x) => !x.key.is_range(),
_ => false,
};
let what = match what {
PublicValue::String(x) => Expr::Table(crate::val::TableName::new(x)),
x => Expr::from_public_value(x),
};
let sql = SelectStatement {
only,
fields: Fields::all(),
what: vec![what],
with: None,
cond: None,
omit: vec![],
split: None,
group: None,
order: None,
limit: None,
start: None,
fetch: None,
version: Expr::Literal(Literal::None),
timeout: Expr::Literal(Literal::None),
explain: None,
tempfiles: false,
};
let ast = Ast::single_expr(Expr::Select(Box::new(sql)));
let vars = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), vars)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn insert(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Insert.to_string()));
}
let (what, data) = extract_args::<(PublicValue, PublicValue)>(params.into_vec())
.ok_or(invalid_params("Expected (what:Value, data:Value)".to_string()))?;
let into = match what {
PublicValue::Null | PublicValue::None => None,
PublicValue::Table(x) => Some(Expr::Table(crate::val::TableName::new(x.into_string()))),
PublicValue::String(x) => Some(Expr::Table(crate::val::TableName::new(x))),
x => Some(Expr::from_public_value(x)),
};
let sql = InsertStatement {
into,
data: SqlData::SingleExpression(Expr::from_public_value(data)),
output: Some(Output::After),
ignore: false,
update: None,
timeout: Expr::Literal(Literal::None),
relation: false,
};
let ast = Ast::single_expr(Expr::Insert(Box::new(sql)));
let var = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), var)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn insert_relation(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::InsertRelation.to_string()));
}
let (what, data) = extract_args::<(PublicValue, PublicValue)>(params.to_vec())
.ok_or(invalid_params("Expected (what, data)".to_string()))?;
let table_name = match what {
PublicValue::Null | PublicValue::None => None,
PublicValue::Table(x) => Some(Expr::Table(crate::val::TableName::new(x.into_string()))),
PublicValue::String(x) => Some(Expr::Table(crate::val::TableName::new(x))),
x => Some(Expr::from_public_value(x)),
};
let data = SqlData::SingleExpression(Expr::from_public_value(data));
let sql = InsertStatement {
relation: true,
into: table_name,
data,
output: Some(Output::After),
ignore: false,
update: None,
timeout: Expr::Literal(Literal::None),
};
let ast = Ast::single_expr(Expr::Insert(Box::new(sql)));
let var = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), var)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn create(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Create.to_string()));
}
let (what, data) = extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (what:Value, data:Value)".to_string()))?;
let only = match what {
PublicValue::String(_) => true,
PublicValue::RecordId(ref x) => !matches!(x.key, PublicRecordIdKey::Range(_)),
_ => false,
};
let data = data
.and_then(|x| {
if x.is_nullish() {
None
} else {
Some(x)
}
})
.map(|x| SqlData::ContentExpression(Expr::from_public_value(x)));
let sql = CreateStatement {
only,
what: vec![value_to_table(what)],
data,
output: Some(Output::After),
timeout: Expr::Literal(Literal::None),
};
let ast = Ast::single_expr(Expr::Create(Box::new(sql)));
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), None)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn upsert(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Upsert.to_string()));
}
let (what, data) = extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (what:Value, data:Value)".to_string()))?;
let only = match what {
PublicValue::RecordId(ref x) => !matches!(x.key, PublicRecordIdKey::Range(_)),
_ => false,
};
let data = data
.and_then(|x| {
if x.is_nullish() {
None
} else {
Some(x)
}
})
.map(|x| SqlData::ContentExpression(Expr::from_public_value(x)));
let sql = UpsertStatement {
only,
what: vec![value_to_table(what)],
data,
output: Some(Output::After),
with: None,
cond: None,
timeout: Expr::Literal(Literal::None),
explain: None,
};
let ast = Ast::single_expr(Expr::Upsert(Box::new(sql)));
let var = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), var)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn update(
&self,
_txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Update.to_string()));
}
let (what, data) = extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (what, data)".to_string()))?;
let only = match what {
PublicValue::RecordId(ref x) => !matches!(x.key, PublicRecordIdKey::Range(_)),
_ => false,
};
let data = data
.and_then(|x| {
if x.is_nullish() {
None
} else {
Some(x)
}
})
.map(|x| SqlData::ContentExpression(Expr::from_public_value(x)));
let sql = UpdateStatement {
only,
what: vec![value_to_table(what)],
data,
output: Some(Output::After),
with: None,
cond: None,
timeout: Expr::Literal(Literal::None),
explain: None,
};
let ast = Ast::single_expr(Expr::Update(Box::new(sql)));
let var = Some(session.variables.clone());
let mut res = self.kvs().process(ast, &session, var).await?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn merge(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Merge.to_string()));
}
let (what, data) = extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (what:Value, data:Value)".to_string()))?;
let only = match what {
PublicValue::RecordId(ref x) => !matches!(x.key, PublicRecordIdKey::Range(_)),
_ => false,
};
let data = data
.and_then(|x| {
if x.is_nullish() {
None
} else {
Some(x)
}
})
.map(|x| SqlData::MergeExpression(Expr::from_public_value(x)));
let sql = UpdateStatement {
only,
what: vec![value_to_table(what)],
data,
output: Some(Output::After),
..Default::default()
};
let ast = Ast::single_expr(Expr::Update(Box::new(sql)));
let var = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), var)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn patch(
&self,
_txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Patch.to_string()));
}
let (what, data, diff) =
extract_args::<(PublicValue, Option<PublicValue>, Option<PublicValue>)>(
params.into_vec(),
)
.ok_or(invalid_params("Expected (what:Value, data:Value, diff:Value)".to_string()))?;
let only = match what {
PublicValue::RecordId(ref x) => !matches!(x.key, PublicRecordIdKey::Range(_)),
_ => false,
};
let data = data
.and_then(|x| {
if x.is_nullish() {
None
} else {
Some(x)
}
})
.map(|x| SqlData::PatchExpression(Expr::from_public_value(x)));
let diff = matches!(diff, Some(PublicValue::Bool(true)));
let expr = Expr::Update(Box::new(UpdateStatement {
only,
what: vec![value_to_table(what)],
data,
output: if diff {
Some(Output::Diff)
} else {
Some(Output::After)
},
with: None,
cond: None,
timeout: Expr::Literal(Literal::None),
explain: None,
}));
let var = Some(session.variables.clone());
let mut res = self.kvs().process(Ast::single_expr(expr), &session, var).await?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn relate(
&self,
_txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Relate.to_string()));
}
let (from, kind, with, data) =
extract_args::<(PublicValue, PublicValue, PublicValue, Option<PublicValue>)>(
params.to_vec(),
)
.ok_or(invalid_params(
"Expected (from:Value, kind:Value, with:Value, data:Value)".to_string(),
))?;
let only = singular(&from) && singular(&with);
let data = data
.and_then(|x| {
if x.is_nullish() {
None
} else {
Some(x)
}
})
.map(|x| SqlData::ContentExpression(Expr::from_public_value(x)));
let expr = Expr::Relate(Box::new(RelateStatement {
only,
or_update: false,
from: Expr::from_public_value(from),
through: value_to_table(kind),
to: Expr::from_public_value(with),
data,
output: Some(Output::After),
timeout: Expr::Literal(Literal::None),
}));
let var = Some(session.variables.clone());
let mut res = self.kvs().process(Ast::single_expr(expr), &session, var).await?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn delete(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Delete.to_string()));
}
let (what,) = extract_args::<(PublicValue,)>(params.into_vec())
.ok_or(invalid_params("Expected (what:Value)".to_string()))?;
let sql = Expr::Delete(Box::new(DeleteStatement {
only: singular(&what),
what: vec![value_to_table(what)],
output: Some(Output::Before),
with: None,
cond: None,
timeout: Expr::Literal(Literal::None),
explain: None,
}));
let ast = Ast::single_expr(sql);
let var = Some(session.variables.clone());
let mut res = run_query(self, txn, session_id, QueryForm::Parsed(ast), var)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn version(
&self,
_txn: Option<Uuid>,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
match params.len() {
0 => Ok(self.version_data()),
_ => Err(invalid_params("Expected 0 arguments".to_string())),
}
}
async fn query(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Query.to_string()));
}
let (query, vars) =
extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (query:string, vars:object)".to_string()))?;
let PublicValue::String(query) = query else {
return Err(invalid_params("Expected query to be string".to_string()));
};
let vars = match vars {
Some(PublicValue::Object(v)) => {
let mut merged = session.variables.clone();
merged.extend(v.into());
Some(merged)
}
None | Some(PublicValue::None | PublicValue::Null) => Some(session.variables.clone()),
unexpected => {
return Err(invalid_params(format!(
"Expected vars to be object, got {unexpected:?}"
)));
}
};
Ok(DbResult::Query(
run_query(self, txn, session_id, QueryForm::Text(&query), vars)
.await
.map_err(types_error_from_anyhow)?,
))
}
async fn gql(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
#[cfg(not(feature = "gql"))]
{
let _ = (txn, session_id, params);
Err(method_not_found(Method::Gql.to_string()))
}
#[cfg(feature = "gql")]
{
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Gql.to_string()));
}
let (query, vars) =
extract_args::<(PublicValue, Option<PublicValue>)>(params.into_vec())
.ok_or(invalid_params("Expected (query:string, vars:object)".to_string()))?;
let PublicValue::String(query) = query else {
return Err(invalid_params("Expected query to be string".to_string()));
};
let vars = match vars {
Some(PublicValue::Object(v)) => {
let mut merged = session.variables.clone();
merged.extend(v.into());
Some(merged)
}
None | Some(PublicValue::None | PublicValue::Null) => {
Some(session.variables.clone())
}
unexpected => {
return Err(invalid_params(format!(
"Expected vars to be object, got {unexpected:?}"
)));
}
};
let plan = self.kvs().parse_gql(&query)?;
Ok(DbResult::Query(
run_query(self, txn, session_id, QueryForm::Plan(plan), vars)
.await
.map_err(types_error_from_anyhow)?,
))
}
}
async fn graphql(
&self,
txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
#[cfg(not(all(feature = "graphql", not(target_family = "wasm"))))]
{
let _ = (txn, session_id, params);
Err(method_not_found(Method::Graphql.to_string()))
}
#[cfg(all(feature = "graphql", not(target_family = "wasm")))]
{
if txn.is_some() {
return Err(invalid_params(
"GraphQL does not support explicit transactions; run it outside begin/commit"
.to_string(),
));
}
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Graphql.to_string()));
}
let (query, variables, operation) =
extract_args::<(PublicValue, Option<PublicValue>, Option<PublicValue>)>(
params.into_vec(),
)
.ok_or(invalid_params(
"Expected (query:string, variables:object, operation:string)".to_string(),
))?;
let PublicValue::String(query) = query else {
return Err(invalid_params("Expected query to be a string".to_string()));
};
let variables = match variables {
Some(v @ PublicValue::Object(_)) => v.into_json_value(),
None | Some(PublicValue::None | PublicValue::Null) => serde_json::Value::Null,
Some(unexpected) => {
return Err(invalid_params(format!(
"Expected variables to be an object, got {unexpected:?}"
)));
}
};
let operation = match operation {
Some(PublicValue::String(s)) => Some(s),
None | Some(PublicValue::None | PublicValue::Null) => None,
Some(unexpected) => {
return Err(invalid_params(format!(
"Expected operation to be a string, got {unexpected:?}"
)));
}
};
let ds = self.kvs_arc();
let json = crate::graphql::execute_request(&ds, &session, query, variables, operation)
.await
.map_err(|e| surrealdb_types::Error::query(e.to_string(), None))?;
Ok(DbResult::Other(crate::rpc::format::json::json_to_value(json)))
}
}
async fn run(
&self,
_txn: Option<Uuid>,
session_id: Uuid,
params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
let session_lock = self.get_session(&session_id)?;
let session = session_lock.read().await;
if !self.kvs().allows_query_by_subject(session.au.as_ref()) {
return Err(method_not_allowed(Method::Run.to_string()));
}
let (name, version, args) = extract_args::<(
PublicValue,
Option<PublicValue>,
Option<PublicValue>,
)>(params.into_vec())
.ok_or(invalid_params("Expected (name:string, version:string, args:array)".to_string()))?;
let name = match name {
PublicValue::String(v) => v,
unexpected => {
return Err(invalid_params(format!(
"Expected name to be string, got {unexpected:?}"
)));
}
};
let version = match version {
Some(PublicValue::String(v)) => Some(v),
None | Some(PublicValue::None | PublicValue::Null) => None,
unexpected => {
return Err(invalid_params(format!(
"Expected version to be string, got {unexpected:?}"
)));
}
};
let args = match args {
Some(PublicValue::Array(args)) => {
args.into_iter().map(Expr::from_public_value).collect::<Vec<Expr>>()
}
None | Some(PublicValue::None | PublicValue::Null) => vec![],
unexpected => {
return Err(invalid_params(format!(
"Expected args to be array, got {unexpected:?}"
)));
}
};
let segments = name.split("::").collect::<Vec<&str>>();
let name = match segments.first() {
Some(&"fn") => Function::Custom(segments[1..].join("::")),
Some(&"mod") => {
if !self
.kvs()
.get_capabilities()
.allows_experimental(&ExperimentalTarget::Surrealism)
{
return Err(invalid_params(
"Experimental capability `surrealism` is not enabled".to_string(),
));
}
let Some(name) = segments.get(1).map(|x| (*x).to_string()) else {
return Err(invalid_params("Expected module name".to_string()));
};
let sub = if segments.len() > 2 {
Some(segments[2..].join("::"))
} else {
None
};
Function::Module(name, sub)
}
Some(&"silo") => {
if !self
.kvs()
.get_capabilities()
.allows_experimental(&ExperimentalTarget::Surrealism)
{
return Err(invalid_params(
"Experimental capability `surrealism` is not enabled".to_string(),
));
}
let Some(org) = segments.get(1).map(|x| (*x).to_string()) else {
return Err(invalid_params("Expected silo organisation name".to_string()));
};
let Some(pkg) = segments.get(2).map(|x| (*x).to_string()) else {
return Err(invalid_params("Expected silo package name".to_string()));
};
let Some(version) = version else {
return Err(invalid_params("Expected silo version".to_string()));
};
let mut split = version.split('.');
let major = split.next().and_then(|s| s.parse::<u32>().ok()).ok_or_else(|| {
invalid_params("Expected major version (u32) in version string".to_string())
})?;
let minor = split.next().and_then(|s| s.parse::<u32>().ok()).ok_or_else(|| {
invalid_params("Expected minor version (u32) in version string".to_string())
})?;
let patch = split.next().and_then(|s| s.parse::<u32>().ok()).ok_or_else(|| {
invalid_params("Expected patch version (u32) in version string".to_string())
})?;
let sub = if segments.len() > 3 {
Some(segments[3..].join("::"))
} else {
None
};
Function::Silo {
org,
pkg,
major,
minor,
patch,
sub,
}
}
Some(&"ml") => {
let name = segments[1..].join("::");
Function::Model(Model {
name: name.into(),
version: version
.ok_or(invalid_params(
"Expected version to be set for model function".to_string(),
))?
.into(),
})
}
_ => Function::Normal(name),
};
let expr = Expr::FunctionCall(Box::new(FunctionCall {
receiver: name,
arguments: args,
}));
let ast = Ast::single_expr(expr);
let var = Some(session.variables.clone());
let mut res = run_query(self, None, session_id, QueryForm::Parsed(ast), var)
.await
.map_err(types_error_from_anyhow)?;
let first = res.remove(0).result?;
Ok(DbResult::Other(first))
}
async fn begin(
&self,
_txn: Option<Uuid>,
_session_id: Uuid,
) -> Result<DbResult, surrealdb_types::Error> {
Err(method_not_allowed(Method::Begin.to_string()))
}
async fn commit(
&self,
_txn: Option<Uuid>,
_session_id: Uuid,
_params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
Err(method_not_allowed(Method::Commit.to_string()))
}
async fn cancel(
&self,
_txn: Option<Uuid>,
_session_id: Uuid,
_params: PublicArray,
) -> Result<DbResult, surrealdb_types::Error> {
Err(method_not_allowed(Method::Cancel.to_string()))
}
}
enum QueryForm<'a> {
Text(&'a str),
Parsed(Ast),
#[cfg(feature = "gql")]
Plan(crate::gql::PreparedGqlQuery),
}
async fn run_query<T>(
this: &T,
txn: Option<Uuid>,
session_id: Uuid,
query: QueryForm<'_>,
vars: Option<PublicVariables>,
) -> Result<Vec<QueryResult>>
where
T: RpcProtocol + ?Sized,
{
let session_lock = this.get_session(&session_id).map_err(anyhow::Error::from)?;
let session = session_lock.read().await;
if !T::LQ_SUPPORT && session.rt {
return Err(bad_lq_config().into());
}
let cancel = this.cancel_handle();
let res = if let Some(txn_id) = txn {
let tx = this.get_tx(txn_id).await.map_err(anyhow::Error::from)?;
match (query, cancel) {
(QueryForm::Text(query), Some(cancel)) => {
this.kvs()
.execute_with_transaction_and_cancel(query, &session, vars, tx, cancel)
.await?
}
(QueryForm::Text(query), None) => {
this.kvs().execute_with_transaction(query, &session, vars, tx).await?
}
(QueryForm::Parsed(ast), Some(cancel)) => {
this.kvs()
.process_with_transaction_and_cancel(ast, &session, vars, tx, cancel)
.await?
}
(QueryForm::Parsed(ast), None) => {
this.kvs().process_with_transaction(ast, &session, vars, tx).await?
}
#[cfg(feature = "gql")]
(QueryForm::Plan(plan), Some(cancel)) => {
this.kvs()
.process_gql_with_transaction_and_cancel(plan, &session, vars, tx, cancel)
.await?
}
#[cfg(feature = "gql")]
(QueryForm::Plan(plan), None) => {
this.kvs().process_gql_with_transaction(plan, &session, vars, tx).await?
}
}
} else {
match (query, cancel) {
(QueryForm::Text(query), Some(cancel)) => {
this.kvs().execute_with_cancel(query, &session, vars, cancel).await?
}
(QueryForm::Text(query), None) => this.kvs().execute(query, &session, vars).await?,
(QueryForm::Parsed(ast), Some(cancel)) => {
this.kvs().process_with_cancel(ast, &session, vars, cancel).await?
}
(QueryForm::Parsed(ast), None) => this.kvs().process(ast, &session, vars).await?,
#[cfg(feature = "gql")]
(QueryForm::Plan(plan), Some(cancel)) => {
this.kvs().process_gql_with_cancel(plan, &session, vars, cancel).await?
}
#[cfg(feature = "gql")]
(QueryForm::Plan(plan), None) => this.kvs().process_gql(plan, &session, vars).await?,
}
};
let live_namespace = session.ns.clone();
let live_database = session.db.clone();
for response in &res {
match &response.query_type {
QueryType::Live => {
if let Ok(PublicValue::Uuid(lqid)) = &response.result {
this.handle_live(
lqid,
session_id,
live_namespace.clone(),
live_database.clone(),
)
.await;
}
}
QueryType::Kill => {
if let Ok(PublicValue::Uuid(lqid)) = &response.result {
this.handle_kill(lqid).await;
}
}
_ => {}
}
}
Ok(res)
}