Skip to main content

surrealdb_core/dbs/
session.rs

1use std::fmt;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use chrono::Utc;
6use surrealdb_types::ToSql;
7use uuid::Uuid;
8
9use crate::iam::{Auth, Level, Role};
10use crate::types::{PublicValue, PublicVariables};
11use crate::val::Value;
12
13/// Specifies the current session information when processing a query.
14#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct Session {
16	/// The current session [`Auth`] information
17	pub au: Arc<Auth>,
18	/// Whether realtime queries are supported
19	pub rt: bool,
20	/// The current connection IP address
21	pub ip: Option<String>,
22	/// The current connection origin
23	pub or: Option<String>,
24	/// The current session ID
25	pub id: Option<Uuid>,
26	/// The currently selected namespace
27	pub ns: Option<String>,
28	/// The currently selected database
29	pub db: Option<String>,
30	/// The current access method
31	pub ac: Option<String>,
32	/// The current authentication token
33	pub tk: Option<PublicValue>,
34	/// The current record authentication data
35	pub rd: Option<PublicValue>,
36	/// The current expiration time of the session
37	pub exp: Option<i64>,
38	/// The variables set
39	pub variables: PublicVariables,
40	/// Strategy for the new streaming planner/executor.
41	pub new_planner_strategy: NewPlannerStrategy,
42	/// When true, EXPLAIN ANALYZE output omits elapsed durations, making
43	/// output deterministic for testing.
44	pub redact_volatile_explain_attrs: bool,
45}
46
47#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
48pub enum NewPlannerStrategy {
49	/// Try the new planner for read-only statements, fall back to compute on Unimplemented.
50	#[default]
51	BestEffortReadOnlyStatements,
52	/// Skip the new planner entirely; always use the compute executor.
53	ComputeOnly,
54	/// Require the new planner for all read-only statements.
55	/// Promotes Error::PlannerUnimplemented to Error::Query (hard error) instead of falling back.
56	AllReadOnlyStatements,
57}
58
59impl fmt::Display for NewPlannerStrategy {
60	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61		match self {
62			Self::BestEffortReadOnlyStatements => f.write_str("best-effort"),
63			Self::ComputeOnly => f.write_str("compute-only"),
64			Self::AllReadOnlyStatements => f.write_str("all-read-only"),
65		}
66	}
67}
68
69impl FromStr for NewPlannerStrategy {
70	type Err = String;
71
72	fn from_str(s: &str) -> Result<Self, Self::Err> {
73		match s {
74			"best-effort" => Ok(Self::BestEffortReadOnlyStatements),
75			"compute-only" => Ok(Self::ComputeOnly),
76			"all-read-only" => Ok(Self::AllReadOnlyStatements),
77			_ => Err(format!(
78				"unknown planner strategy: '{s}' (expected 'best-effort', 'compute-only', or 'all-read-only')"
79			)),
80		}
81	}
82}
83
84impl Session {
85	/// Set the selected namespace for the session
86	pub fn with_ns(mut self, ns: &str) -> Session {
87		self.ns = Some(ns.to_owned());
88		self
89	}
90
91	/// Set the selected database for the session
92	pub fn with_db(mut self, db: &str) -> Session {
93		self.db = Some(db.to_owned());
94		self
95	}
96
97	/// Set the selected access method for the session
98	pub fn with_ac(mut self, ac: &str) -> Session {
99		self.ac = Some(ac.to_owned());
100		self
101	}
102
103	// Set the realtime functionality of the session
104	pub fn with_rt(mut self, rt: bool) -> Session {
105		self.rt = rt;
106		self
107	}
108
109	/// Set the new planner strategy for the session
110	pub fn new_planner_strategy(mut self, strategy: NewPlannerStrategy) -> Session {
111		self.new_planner_strategy = strategy;
112		self
113	}
114
115	/// Retrieves the selected namespace
116	pub(crate) fn ns(&self) -> Option<Arc<str>> {
117		self.ns.as_deref().map(Into::into)
118	}
119
120	/// Retrieves the selected database
121	pub(crate) fn db(&self) -> Option<Arc<str>> {
122		self.db.as_deref().map(Into::into)
123	}
124
125	/// Checks if live queries are allowed
126	pub(crate) fn live(&self) -> bool {
127		self.rt
128	}
129
130	/// Checks if the session has expired
131	pub(crate) fn expired(&self) -> bool {
132		match self.exp {
133			Some(exp) => Utc::now().timestamp() > exp,
134			// It is currently possible to have sessions without expiration.
135			None => false,
136		}
137	}
138
139	pub(crate) fn values(&self) -> Vec<(&'static str, Value)> {
140		use crate::sql::expression::convert_public_value_to_internal;
141
142		let access = self.ac.clone().map(|x| x.into()).unwrap_or(Value::None);
143		let auth = self.rd.clone().map(convert_public_value_to_internal).unwrap_or(Value::None);
144		let token = self.tk.clone().map(convert_public_value_to_internal).unwrap_or(Value::None);
145		let session = Value::from(map! {
146			"ac".to_string() => access.clone(),
147			"exp".to_string() => self.exp.map(Value::from).unwrap_or(Value::None),
148			"db".to_string() => self.db.clone().map(|x| x.into()).unwrap_or(Value::None),
149			"id".to_string() => self.id.map(|x| Value::Uuid(x.into())).unwrap_or(Value::None),
150			"ip".to_string() => self.ip.clone().map(|x| x.into()).unwrap_or(Value::None),
151			"ns".to_string() => self.ns.clone().map(|x| x.into()).unwrap_or(Value::None),
152			"or".to_string() => self.or.clone().map(|x| x.into()).unwrap_or(Value::None),
153			"rd".to_string() => auth.clone(),
154			"tk".to_string() => token.clone(),
155		});
156
157		vec![("access", access), ("auth", auth), ("token", token), ("session", session)]
158	}
159
160	/// Create a system session for a given level and role
161	pub fn for_level(level: Level, role: Role) -> Session {
162		// Create a new session
163		let mut sess = Session::default();
164		// Set the session details
165		match level {
166			Level::Root => {
167				sess.au = Arc::new(Auth::for_root(role));
168			}
169			Level::Namespace(ns) => {
170				sess.au = Arc::new(Auth::for_ns(role, &ns));
171				sess.ns = Some(ns);
172			}
173			Level::Database(ns, db) => {
174				sess.au = Arc::new(Auth::for_db(role, &ns, &db));
175				sess.ns = Some(ns);
176				sess.db = Some(db);
177			}
178			_ => {}
179		}
180		sess
181	}
182
183	/// Create a record user session for a given NS and DB
184	pub fn for_record(ns: &str, db: &str, ac: &str, rid: PublicValue) -> Session {
185		Session {
186			ac: Some(ac.to_owned()),
187			au: Arc::new(Auth::for_record(rid.to_sql(), ns, db, ac)),
188			rt: false,
189			ip: None,
190			or: None,
191			id: None,
192			ns: Some(ns.to_owned()),
193			db: Some(db.to_owned()),
194			tk: None,
195			rd: Some(rid),
196			exp: None,
197			variables: Default::default(),
198			new_planner_strategy: NewPlannerStrategy::default(),
199			redact_volatile_explain_attrs: false,
200		}
201	}
202
203	/// Create a system session for the root level with Owner role
204	pub fn owner() -> Session {
205		Session::for_level(Level::Root, Role::Owner)
206	}
207
208	/// Create a system session for the root level with Editor role
209	pub fn editor() -> Session {
210		Session::for_level(Level::Root, Role::Editor)
211	}
212
213	/// Create a system session for the root level with Viewer role
214	pub fn viewer() -> Session {
215		Session::for_level(Level::Root, Role::Viewer)
216	}
217}