surrealdb_sql/ctx/
context.rs

1use crate::ctx::canceller::Canceller;
2use crate::ctx::reason::Reason;
3use crate::dbs::capabilities::FuncTarget;
4#[cfg(feature = "http")]
5use crate::dbs::capabilities::NetTarget;
6use crate::dbs::{Capabilities, Notification};
7use crate::err::Error;
8use crate::idx::planner::QueryPlanner;
9use crate::value::Value;
10use channel::Sender;
11use std::borrow::Cow;
12use std::collections::HashMap;
13use std::fmt::{self, Debug};
14use std::str::FromStr;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::Arc;
17use std::time::Duration;
18use trice::Instant;
19#[cfg(feature = "http")]
20use url::Url;
21
22impl<'a> From<Value> for Cow<'a, Value> {
23	fn from(v: Value) -> Cow<'a, Value> {
24		Cow::Owned(v)
25	}
26}
27
28impl<'a> From<&'a Value> for Cow<'a, Value> {
29	fn from(v: &'a Value) -> Cow<'a, Value> {
30		Cow::Borrowed(v)
31	}
32}
33pub struct Context<'a> {
34	// An optional parent context.
35	parent: Option<&'a Context<'a>>,
36	// An optional deadline.
37	deadline: Option<Instant>,
38	// Whether or not this context is cancelled.
39	cancelled: Arc<AtomicBool>,
40	// A collection of read only values stored in this context.
41	values: HashMap<Cow<'static, str>, Cow<'a, Value>>,
42	// Stores the notification channel if available
43	notifications: Option<Sender<Notification>>,
44	// An optional query planner
45	query_planner: Option<&'a QueryPlanner<'a>>,
46	// Capabilities
47	capabilities: Arc<Capabilities>,
48}
49
50impl<'a> Default for Context<'a> {
51	fn default() -> Self {
52		Context::background()
53	}
54}
55
56impl<'a> Debug for Context<'a> {
57	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58		f.debug_struct("Context")
59			.field("parent", &self.parent)
60			.field("deadline", &self.deadline)
61			.field("cancelled", &self.cancelled)
62			.field("values", &self.values)
63			.finish()
64	}
65}
66
67impl<'a> Context<'a> {
68	/// Create an empty background context.
69	pub fn background() -> Self {
70		Context {
71			values: HashMap::default(),
72			parent: None,
73			deadline: None,
74			cancelled: Arc::new(AtomicBool::new(false)),
75			notifications: None,
76			query_planner: None,
77			capabilities: Arc::new(Capabilities::default()),
78		}
79	}
80
81	/// Create a new child from a frozen context.
82	pub fn new(parent: &'a Context) -> Self {
83		Context {
84			values: HashMap::default(),
85			parent: Some(parent),
86			deadline: parent.deadline,
87			cancelled: Arc::new(AtomicBool::new(false)),
88			notifications: parent.notifications.clone(),
89			query_planner: parent.query_planner,
90			capabilities: parent.capabilities.clone(),
91		}
92	}
93
94	/// Add a value to the context. It overwrites any previously set values
95	/// with the same key.
96	pub fn add_value<K, V>(&mut self, key: K, value: V)
97	where
98		K: Into<Cow<'static, str>>,
99		V: Into<Cow<'a, Value>>,
100	{
101		self.values.insert(key.into(), value.into());
102	}
103
104	/// Add cancellation to the context. The value that is returned will cancel
105	/// the context and it's children once called.
106	pub fn add_cancel(&mut self) -> Canceller {
107		let cancelled = self.cancelled.clone();
108		Canceller::new(cancelled)
109	}
110
111	/// Add a deadline to the context. If the current deadline is sooner than
112	/// the provided deadline, this method does nothing.
113	pub fn add_deadline(&mut self, deadline: Instant) {
114		match self.deadline {
115			Some(current) if current < deadline => (),
116			_ => self.deadline = Some(deadline),
117		}
118	}
119
120	/// Add a timeout to the context. If the current timeout is sooner than
121	/// the provided timeout, this method does nothing. If the result of the
122	/// addition causes an overflow, this method returns an error.
123	pub fn add_timeout(&mut self, timeout: Duration) -> Result<(), Error> {
124		match Instant::now().checked_add(timeout) {
125			Some(deadline) => {
126				self.add_deadline(deadline);
127				Ok(())
128			}
129			None => Err(Error::InvalidTimeout(timeout.as_secs())),
130		}
131	}
132
133	/// Add the LIVE query notification channel to the context, so that we
134	/// can send notifications to any subscribers.
135	pub fn add_notifications(&mut self, chn: Option<&Sender<Notification>>) {
136		self.notifications = chn.cloned()
137	}
138
139	/// Set the query planner
140	pub(crate) fn set_query_planner(&mut self, qp: &'a QueryPlanner) {
141		self.query_planner = Some(qp);
142	}
143
144	/// Get the timeout for this operation, if any. This is useful for
145	/// checking if a long job should be started or not.
146	pub fn timeout(&self) -> Option<Duration> {
147		self.deadline.map(|v| v.saturating_duration_since(Instant::now()))
148	}
149
150	pub fn notifications(&self) -> Option<Sender<Notification>> {
151		self.notifications.clone()
152	}
153
154	pub(crate) fn get_query_planner(&self) -> Option<&QueryPlanner> {
155		self.query_planner
156	}
157
158	/// Check if the context is done. If it returns `None` the operation may
159	/// proceed, otherwise the operation should be stopped.
160	pub fn done(&self) -> Option<Reason> {
161		match self.deadline {
162			Some(deadline) if deadline <= Instant::now() => Some(Reason::Timedout),
163			_ if self.cancelled.load(Ordering::Relaxed) => Some(Reason::Canceled),
164			_ => match self.parent {
165				Some(ctx) => ctx.done(),
166				_ => None,
167			},
168		}
169	}
170
171	/// Check if the context is ok to continue.
172	pub fn is_ok(&self) -> bool {
173		self.done().is_none()
174	}
175
176	/// Check if the context is not ok to continue.
177	pub fn is_done(&self) -> bool {
178		self.done().is_some()
179	}
180
181	/// Check if the context is not ok to continue, because it timed out.
182	pub fn is_timedout(&self) -> bool {
183		matches!(self.done(), Some(Reason::Timedout))
184	}
185
186	/// Get a value from the context. If no value is stored under the
187	/// provided key, then this will return None.
188	pub fn value(&self, key: &str) -> Option<&Value> {
189		match self.values.get(key) {
190			Some(v) => match v {
191				Cow::Borrowed(v) => Some(*v),
192				Cow::Owned(v) => Some(v),
193			},
194			None => match self.parent {
195				Some(p) => p.value(key),
196				_ => None,
197			},
198		}
199	}
200
201	/// Get a 'static view into the cancellation status.
202	#[cfg(feature = "scripting")]
203	pub fn cancellation(&self) -> crate::ctx::cancellation::Cancellation {
204		crate::ctx::cancellation::Cancellation::new(
205			self.deadline,
206			std::iter::successors(Some(self), |ctx| ctx.parent)
207				.map(|ctx| ctx.cancelled.clone())
208				.collect(),
209		)
210	}
211
212	//
213	// Capabilities
214	//
215
216	/// Set the capabilities for this context
217	pub fn add_capabilities(&mut self, caps: Capabilities) {
218		self.capabilities = Arc::new(caps);
219	}
220
221	/// Get the capabilities for this context
222	#[allow(dead_code)]
223	pub fn get_capabilities(&self) -> Arc<Capabilities> {
224		self.capabilities.clone()
225	}
226
227	/// Check if scripting is allowed
228	#[allow(dead_code)]
229	pub fn check_allowed_scripting(&self) -> Result<(), Error> {
230		if !self.capabilities.allows_scripting() {
231			return Err(Error::ScriptingNotAllowed);
232		}
233		Ok(())
234	}
235
236	/// Check if a function is allowed
237	pub fn check_allowed_function(&self, target: &str) -> Result<(), Error> {
238		let func_target = FuncTarget::from_str(target).map_err(|_| Error::InvalidFunction {
239			name: target.to_string(),
240			message: "Invalid function name".to_string(),
241		})?;
242
243		if !self.capabilities.allows_function(&func_target) {
244			return Err(Error::FunctionNotAllowed(target.to_string()));
245		}
246		Ok(())
247	}
248
249	/// Check if a network target is allowed
250	#[cfg(feature = "http")]
251	pub fn check_allowed_net(&self, target: &Url) -> Result<(), Error> {
252		match target.host() {
253			Some(host)
254				if self.capabilities.allows_network_target(&NetTarget::Host(
255					host.to_owned(),
256					target.port_or_known_default(),
257				)) =>
258			{
259				Ok(())
260			}
261			_ => Err(Error::NetTargetNotAllowed(target.to_string())),
262		}
263	}
264}