surrealdb/api/
mod.rs

1//! Functionality for connecting to local and remote databases
2
3pub mod engine;
4pub mod err;
5pub mod method;
6pub mod opt;
7
8mod conn;
9
10pub use method::query::Response;
11
12use crate::api::conn::DbResponse;
13use crate::api::conn::Router;
14use crate::api::err::Error;
15use crate::api::opt::Endpoint;
16use semver::BuildMetadata;
17use semver::VersionReq;
18use std::fmt::Debug;
19use std::future::Future;
20use std::future::IntoFuture;
21use std::marker::PhantomData;
22use std::pin::Pin;
23use std::sync::Arc;
24use std::sync::OnceLock;
25
26/// A specialized `Result` type
27pub type Result<T> = std::result::Result<T, crate::Error>;
28
29const SUPPORTED_VERSIONS: (&str, &str) = (">=1.0.0-beta.9, <2.0.0", "20230701.55918b7c");
30
31/// Connection trait implemented by supported engines
32pub trait Connection: conn::Connection {}
33
34/// The future returned when creating a new SurrealDB instance
35#[derive(Debug)]
36#[must_use = "futures do nothing unless you `.await` or poll them"]
37pub struct Connect<C: Connection, Response> {
38	router: Arc<OnceLock<Router<C>>>,
39	address: Result<Endpoint>,
40	capacity: usize,
41	client: PhantomData<C>,
42	response_type: PhantomData<Response>,
43}
44
45impl<C, R> Connect<C, R>
46where
47	C: Connection,
48{
49	/// Sets the maximum capacity of the connection
50	///
51	/// This is used to set bounds of the channels used internally
52	/// as well set the capacity of the `HashMap` used for routing
53	/// responses in case of the WebSocket client.
54	///
55	/// Setting this capacity to `0` (the default) means that
56	/// unbounded channels will be used. If your queries per second
57	/// are so high that the client is running out of memory,
58	/// it might be helpful to set this to a number that works best
59	/// for you.
60	///
61	/// # Examples
62	///
63	/// ```no_run
64	/// # #[tokio::main]
65	/// # async fn main() -> surrealdb::Result<()> {
66	/// use surrealdb::engine::remote::ws::Ws;
67	/// use surrealdb::Surreal;
68	///
69	/// let db = Surreal::new::<Ws>("localhost:8000")
70	///     .with_capacity(100_000)
71	///     .await?;
72	/// # Ok(())
73	/// # }
74	/// ```
75	pub const fn with_capacity(mut self, capacity: usize) -> Self {
76		self.capacity = capacity;
77		self
78	}
79}
80
81impl<Client> IntoFuture for Connect<Client, Surreal<Client>>
82where
83	Client: Connection,
84{
85	type Output = Result<Surreal<Client>>;
86	type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync>>;
87
88	fn into_future(self) -> Self::IntoFuture {
89		Box::pin(async move {
90			let client = Client::connect(self.address?, self.capacity).await?;
91			client.check_server_version().await?;
92			Ok(client)
93		})
94	}
95}
96
97impl<Client> IntoFuture for Connect<Client, ()>
98where
99	Client: Connection,
100{
101	type Output = Result<()>;
102	type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync>>;
103
104	fn into_future(self) -> Self::IntoFuture {
105		Box::pin(async move {
106			// Avoid establishing another connection if already connected
107			if self.router.get().is_some() {
108				return Err(Error::AlreadyConnected.into());
109			}
110			let arc = Client::connect(self.address?, self.capacity).await?.router;
111			let cell = Arc::into_inner(arc).expect("new connection to have no references");
112			let router = cell.into_inner().expect("router to be set");
113			self.router.set(router).map_err(|_| Error::AlreadyConnected)?;
114			let client = Surreal {
115				router: self.router,
116			};
117			client.check_server_version().await?;
118			Ok(())
119		})
120	}
121}
122
123#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
124pub(crate) enum ExtraFeatures {
125	Backup,
126	LiveQueries,
127}
128
129/// A database client instance for embedded or remote databases
130#[derive(Debug)]
131pub struct Surreal<C: Connection> {
132	router: Arc<OnceLock<Router<C>>>,
133}
134
135impl<C> Surreal<C>
136where
137	C: Connection,
138{
139	async fn check_server_version(&self) -> Result<()> {
140		let (versions, build_meta) = SUPPORTED_VERSIONS;
141		// invalid version requirements should be caught during development
142		let req = VersionReq::parse(versions).expect("valid supported versions");
143		let build_meta = BuildMetadata::new(build_meta).expect("valid supported build metadata");
144		let version = self.version().await?;
145		let server_build = &version.build;
146		if !req.matches(&version) {
147			return Err(Error::VersionMismatch {
148				server_version: version,
149				supported_versions: versions.to_owned(),
150			}
151			.into());
152		} else if !server_build.is_empty() && server_build < &build_meta {
153			return Err(Error::BuildMetadataMismatch {
154				server_metadata: server_build.clone(),
155				supported_metadata: build_meta,
156			}
157			.into());
158		}
159		Ok(())
160	}
161}
162
163impl<C> Clone for Surreal<C>
164where
165	C: Connection,
166{
167	fn clone(&self) -> Self {
168		Self {
169			router: self.router.clone(),
170		}
171	}
172}
173
174trait OnceLockExt<C>
175where
176	C: Connection,
177{
178	fn with_value(value: Router<C>) -> OnceLock<Router<C>> {
179		let cell = OnceLock::new();
180		match cell.set(value) {
181			Ok(()) => cell,
182			Err(_) => unreachable!("don't have exclusive access to `cell`"),
183		}
184	}
185
186	fn extract(&self) -> Result<&Router<C>>;
187}
188
189impl<C> OnceLockExt<C> for OnceLock<Router<C>>
190where
191	C: Connection,
192{
193	fn extract(&self) -> Result<&Router<C>> {
194		let router = self.get().ok_or(Error::ConnectionUninitialised)?;
195		Ok(router)
196	}
197}