edgedb_composable_query/composable/
mod.rs1use crate::{EdgedbObject, EdgedbPrim, EdgedbQueryArgs, EdgedbSetValue, Ref};
101
102pub use edgedb_composable_query_derive::{EdgedbComposableQuery, EdgedbComposableSelector};
103use edgedb_tokio::Client;
104use nonempty::NonEmpty;
105
106use crate::Result;
107
108pub enum ComposableQueryResultKind {
109 Field,
110 Selector,
111 FreeObject,
112}
113
114pub trait EdgedbComposableSelector {
116 const RESULT_TYPE: ComposableQueryResultKind;
117
118 fn format_selector(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error>;
120
121 fn format_subquery(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
122 match Self::RESULT_TYPE {
123 ComposableQueryResultKind::Field => {
124 return Ok(());
125 }
126 ComposableQueryResultKind::Selector => fmt.write_str(": {\n")?,
127 ComposableQueryResultKind::FreeObject => fmt.write_str(" := {\n")?,
128 };
129
130 Self::format_selector(fmt)?;
131
132 fmt.write_str("\n}")
133 }
134}
135
136impl<T: EdgedbPrim> EdgedbComposableSelector for T {
137 const RESULT_TYPE: ComposableQueryResultKind = ComposableQueryResultKind::Field;
138
139 fn format_selector(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
140 Ok(())
141 }
142
143 fn format_subquery(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
144 Ok(())
145 }
146}
147
148impl<T: EdgedbComposableSelector> EdgedbComposableSelector for Vec<T> {
149 const RESULT_TYPE: ComposableQueryResultKind = T::RESULT_TYPE;
150
151 fn format_selector(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
152 T::format_selector(fmt)
153 }
154}
155
156impl<T: EdgedbComposableSelector> EdgedbComposableSelector for Option<T> {
157 const RESULT_TYPE: ComposableQueryResultKind = T::RESULT_TYPE;
158
159 fn format_selector(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
160 T::format_selector(fmt)
161 }
162}
163
164impl<T: EdgedbComposableSelector> EdgedbComposableSelector for NonEmpty<T> {
165 const RESULT_TYPE: ComposableQueryResultKind = T::RESULT_TYPE;
166
167 fn format_selector(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
168 T::format_selector(fmt)
169 }
170}
171
172impl<T: EdgedbComposableSelector + EdgedbObject> EdgedbComposableSelector for Ref<T> {
173 const RESULT_TYPE: ComposableQueryResultKind = ComposableQueryResultKind::Selector;
174
175 fn format_selector(fmt: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
176 fmt.write_str("\tid,\n")?;
177
178 T::format_selector(fmt)?;
179
180 Ok(())
181 }
182}
183
184pub trait EdgedbComposableQuery {
186 const ARG_NAMES: &'static [&'static str];
187
188 type ArgTypes: EdgedbQueryArgs;
189 type ReturnType: EdgedbSetValue;
190
191 fn format_query(
192 fmt: &mut impl std::fmt::Write,
193 args: &::std::collections::HashMap<&str, String>,
194 ) -> Result<(), std::fmt::Error>;
195
196 fn query() -> String {
197 let mut buf = String::new();
198 let args = Self::ARG_NAMES
203 .iter()
204 .enumerate()
205 .map(|(i, n)| (*n, format!("${i}")))
206 .collect();
207
208 Self::format_query(&mut buf, &args).unwrap();
209 buf
210 }
211}
212
213pub async fn run_query<T: EdgedbComposableQuery>(
215 client: &Client,
216 args: T::ArgTypes,
217) -> Result<T::ReturnType>
218where
219 <T as EdgedbComposableQuery>::ArgTypes: Send,
220{
221 let query_s = T::query();
222
223 crate::query(client, &query_s, args).await
224}
225
226#[cfg(test)]
227mod test {
228 use edgedb_protocol::model::Uuid;
229
230 use crate::composable::EdgedbComposableQuery;
231 use crate::composable::EdgedbComposableSelector;
232 use crate::{EdgedbObject, Ref};
233
234 #[derive(
235 Debug, PartialEq, Eq, EdgedbObject, EdgedbComposableSelector, EdgedbComposableQuery,
236 )]
237 #[select("select Inner limit 1")]
238 struct InnerQuery {
239 req: String,
240 opt: Option<String>,
241 }
242
243 #[derive(Debug, PartialEq, Eq, EdgedbObject, EdgedbComposableSelector)]
244 struct InnerSelector {
245 req: String,
246 opt: Option<String>,
247 }
248
249 #[derive(Debug, PartialEq, Eq, EdgedbComposableQuery)]
250 #[select("select Inner limit 1")]
251 struct OneInnerBySelector(InnerSelector);
252
253 #[derive(Debug, PartialEq, Eq, EdgedbComposableQuery)]
254 #[params(id: Uuid)]
255 #[select("select Inner filter .id = id")]
256 struct OneInnerBySelectorById(InnerSelector);
257
258 #[derive(Debug, PartialEq, Eq, EdgedbComposableQuery)]
259 #[select("select Inner limit 10")]
260 struct ManyInnersBySelector(Vec<InnerSelector>);
261
262 #[derive(
263 Debug, PartialEq, Eq, EdgedbObject, EdgedbComposableSelector, EdgedbComposableQuery,
264 )]
265 #[select("select Outer limit 1")]
266 struct OuterQuery {
267 inner: Option<InnerSelector>,
268
269 some_field: Option<String>,
270 other_field: String,
271 }
272
273 #[derive(
274 Debug, PartialEq, Eq, EdgedbObject, EdgedbComposableSelector, EdgedbComposableQuery,
275 )]
276 #[select("select Outer limit 1")]
277 struct OuterQueryWithRef {
278 inner: Option<Ref<InnerSelector>>,
279
280 some_field: Option<String>,
281 other_field: String,
282 }
283
284 #[test]
285 fn selector_tests() {
286 let mut buf = String::new();
299 InnerSelector::format_selector(&mut buf).unwrap();
300 insta::assert_snapshot!(buf);
301
302 let mut buf = String::new();
303 Option::<InnerSelector>::format_selector(&mut buf).unwrap();
304 insta::assert_snapshot!(buf);
305 }
306
307 #[test]
308 fn query_tests() {
309 insta::assert_snapshot!(InnerQuery::query());
310 insta::assert_snapshot!(OuterQuery::query());
311 insta::assert_snapshot!(OuterQueryWithRef::query());
312
313 insta::assert_snapshot!(OneInnerBySelector::query());
314 insta::assert_snapshot!(OneInnerBySelectorById::query());
315 insta::assert_snapshot!(ManyInnersBySelector::query());
316 }
317}