surrealdb-core 3.2.0

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
use revision::revisioned;
use surrealdb_strand::Strand;
use surrealdb_types::{SqlFormat, ToSql};

use crate::catalog::auth::AuthLimit;
use crate::expr::Expr;
use crate::expr::statements::info::InfoStructure;
use crate::kvs::impl_kv_value_revisioned;
use crate::sql::statements::define::DefineKind;
use crate::sql::{self};
use crate::val::{TableName, Value};

#[revisioned(revision = 3)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub struct EventDefinition {
	pub(crate) name: Strand,
	pub(crate) target_table: TableName,
	pub(crate) when: Expr,
	pub(crate) then: Vec<Expr>,
	pub(crate) comment: Option<String>,
	/// The auth limit of the API.
	#[revision(start = 2, default_fn = "default_auth_limit")]
	pub(crate) auth_limit: AuthLimit,
	/// Whether this event should be queued for async processing.
	#[revision(start = 3, default_fn = "default_event_kind")]
	pub(crate) kind: EventKind,
}

#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum EventKind {
	Sync,
	Async {
		/// Maximum retry count for async events (0 disables retries; event still runs once).
		retry: u16,
		/// Maximum async event nesting depth for this event (0 allows top-level only).
		max_depth: u16,
	},
}

// This was pushed in after the first beta, so we need to add auth_limit to structs in a
// non-breaking way
impl EventDefinition {
	pub(crate) const DEFAULT_RETRY: u16 = 1;
	pub(crate) const DEFAULT_MAX_DEPTH: u16 = 3;

	fn default_auth_limit(_revision: u16) -> Result<AuthLimit, revision::Error> {
		Ok(AuthLimit::new_no_limit())
	}

	fn default_event_kind(_revision: u16) -> Result<EventKind, revision::Error> {
		Ok(EventKind::Sync)
	}
}

impl_kv_value_revisioned!(EventDefinition);

impl EventDefinition {
	/// Returns true if the event is asynchronous.
	pub(crate) fn is_async(&self) -> bool {
		matches!(self.kind, EventKind::Async { .. })
	}

	pub(crate) fn to_sql_definition(&self) -> sql::DefineEventStatement {
		sql::DefineEventStatement {
			kind: DefineKind::Default,
			name: sql::Expr::Idiom(sql::Idiom::field(self.name.clone())),
			target_table: sql::Expr::Table(self.target_table.clone()),
			when: self.when.clone().into(),
			then: self.then.iter().cloned().map(Into::into).collect(),
			comment: self
				.comment
				.clone()
				.map(|v| sql::Expr::Literal(sql::Literal::String(v.into())))
				.unwrap_or(sql::Expr::Literal(sql::Literal::None)),
			event_kind: self.kind.clone(),
		}
	}

	pub(crate) fn retry(&self) -> u16 {
		match self.kind {
			EventKind::Sync => 0,
			EventKind::Async {
				retry,
				..
			} => retry,
		}
	}

	pub(crate) fn max_depth(&self) -> u16 {
		match self.kind {
			EventKind::Sync => 0,
			EventKind::Async {
				max_depth,
				..
			} => max_depth,
		}
	}
}

impl InfoStructure for EventDefinition {
	fn structure(self) -> Value {
		let mut map = map! {
			"name" => self.name.into(),
			"what" => self.target_table.into(),
			"when" => self.when.structure(),
			"then" => self.then.into_iter().map(|x| x.structure()).collect(),
			"comment", if let Some(v) = self.comment => v.into(),
		};
		if let EventKind::Async {
			retry,
			max_depth,
		} = &self.kind
		{
			map.insert("async", Value::Bool(true));
			map.insert("retry", (*retry).into());
			map.insert("maxdepth", (*max_depth).into());
		}
		Value::from(map)
	}
}

impl ToSql for EventDefinition {
	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
		self.to_sql_definition().fmt_sql(f, fmt)
	}
}