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#[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 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 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 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 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 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 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}