Skip to main content

surrealdb/api/method/
upsert.rs

1use std::borrow::Cow;
2use std::future::IntoFuture;
3use std::marker::PhantomData;
4
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7use uuid::Uuid;
8
9use super::transaction::WithTransaction;
10use super::validate_data;
11use crate::api::conn::Command;
12use crate::api::method::{BoxFuture, Content, Merge, Patch};
13use crate::api::opt::{PatchOp, Resource};
14use crate::api::{self, Connection, Result};
15use crate::core::val;
16use crate::method::OnceLockExt;
17use crate::opt::KeyRange;
18use crate::{Surreal, Value};
19
20/// An upsert future
21#[derive(Debug)]
22#[must_use = "futures do nothing unless you `.await` or poll them"]
23pub struct Upsert<'r, C: Connection, R> {
24	pub(super) txn: Option<Uuid>,
25	pub(super) client: Cow<'r, Surreal<C>>,
26	pub(super) resource: Result<Resource>,
27	pub(super) response_type: PhantomData<R>,
28}
29
30impl<C, R> WithTransaction for Upsert<'_, C, R>
31where
32	C: Connection,
33{
34	fn with_transaction(mut self, id: Uuid) -> Self {
35		self.txn = Some(id);
36		self
37	}
38}
39
40impl<C, R> Upsert<'_, C, R>
41where
42	C: Connection,
43{
44	/// Converts to an owned type which can easily be moved to a different
45	/// thread
46	pub fn into_owned(self) -> Upsert<'static, C, R> {
47		Upsert {
48			client: Cow::Owned(self.client.into_owned()),
49			..self
50		}
51	}
52}
53
54macro_rules! into_future {
55	($method:ident) => {
56		fn into_future(self) -> Self::IntoFuture {
57			let Upsert {
58				txn,
59				client,
60				resource,
61				..
62			} = self;
63			Box::pin(async move {
64				let router = client.inner.router.extract()?;
65				router
66					.$method(Command::Upsert {
67						txn,
68						what: resource?,
69						data: None,
70					})
71					.await
72			})
73		}
74	};
75}
76
77impl<'r, Client> IntoFuture for Upsert<'r, Client, Value>
78where
79	Client: Connection,
80{
81	type Output = Result<Value>;
82	type IntoFuture = BoxFuture<'r, Self::Output>;
83
84	into_future! {execute_value}
85}
86
87impl<'r, Client, R> IntoFuture for Upsert<'r, Client, Option<R>>
88where
89	Client: Connection,
90	R: DeserializeOwned,
91{
92	type Output = Result<Option<R>>;
93	type IntoFuture = BoxFuture<'r, Self::Output>;
94
95	into_future! {execute_opt}
96}
97
98impl<'r, Client, R> IntoFuture for Upsert<'r, Client, Vec<R>>
99where
100	Client: Connection,
101	R: DeserializeOwned,
102{
103	type Output = Result<Vec<R>>;
104	type IntoFuture = BoxFuture<'r, Self::Output>;
105
106	into_future! {execute_vec}
107}
108
109impl<C> Upsert<'_, C, Value>
110where
111	C: Connection,
112{
113	/// Restricts the records to upsert to those in the specified range
114	pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
115		self.resource = self.resource.and_then(|x| x.with_range(range.into()));
116		self
117	}
118}
119
120impl<C, R> Upsert<'_, C, Vec<R>>
121where
122	C: Connection,
123{
124	/// Restricts the records to upsert to those in the specified range
125	pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
126		self.resource = self.resource.and_then(|x| x.with_range(range.into()));
127		self
128	}
129}
130
131impl<'r, C, R> Upsert<'r, C, R>
132where
133	C: Connection,
134	R: DeserializeOwned,
135{
136	/// Replaces the current document / record data with the specified data
137	pub fn content<D>(self, data: D) -> Content<'r, C, R>
138	where
139		D: Serialize + 'static,
140	{
141		Content::from_closure(self.client, self.txn, || {
142			let data = api::value::to_core_value(data)?;
143
144			validate_data(
145				&data,
146				"Tried to upsert non-object-like data as content, only structs and objects are supported",
147			)?;
148
149			let data = match data {
150				val::Value::None => None,
151				content => Some(content),
152			};
153
154			Ok(Command::Upsert {
155				txn: self.txn,
156				what: self.resource?,
157				data,
158			})
159		})
160	}
161
162	/// Merges the current document / record data with the specified data
163	pub fn merge<D>(self, data: D) -> Merge<'r, C, D, R>
164	where
165		D: Serialize,
166	{
167		Merge {
168			txn: self.txn,
169			client: self.client,
170			resource: self.resource,
171			content: data,
172			upsert: true,
173			response_type: PhantomData,
174		}
175	}
176
177	/// Patches the current document / record data with the specified JSON Patch
178	/// data
179	pub fn patch(self, patch: impl Into<PatchOp>) -> Patch<'r, C, R> {
180		let PatchOp(result) = patch.into();
181		let patches = match result {
182			Ok(serde_content::Value::Seq(values)) => values.into_iter().map(Ok).collect(),
183			Ok(value) => vec![Ok(value)],
184			Err(error) => vec![Err(error)],
185		};
186		Patch {
187			patches,
188			txn: self.txn,
189			client: self.client,
190			resource: self.resource,
191			upsert: true,
192			response_type: PhantomData,
193		}
194	}
195}