edgedb_composable_query/lib.rs
1//! # edgedb-composable-query
2//!
3//! Query arbitrary structs from EdgeDB. Compose queries of arbitrary complexity.
4//!
5//! Beware: it's very much a work-in-progress. Pre-0.1. It's messy,
6//! there're todo!()'s in the code, etc. I'm still figuring out the semantics.
7//! If you're interested in this working for your use-cases, please
8//! try it and file the issues at: <https://github.com/valyagolev/edgedb-composable-query/issues>.
9//! But don't use it seriously yet; it might panic, and *will* change the API.
10//!
11//! Two major parts of the crate.
12//!
13//! 1. A set of tools, around the [`EdgedbObject`] derivable trait, that allow you to query
14//! arbitrary rust structs from EdgeDB, converting types automatically. See examples below.
15//! 2. A set of tools, around the [`composable::EdgedbComposableQuery`] derivable trait, that allow you express
16//! complex, composable queries through Rust structs and attributes. See docs and examples in the [composable] submodule.
17//!
18//! # EdgedbObject Examples
19//!
20//! If you have this schema:
21//!
22//! ```edgedb
23//! module default {
24//! type Inner {
25//! required req: str;
26//! opt: str;
27//! }
28//! type Outer {
29//! inner: Inner;
30//!
31//! some_field: str;
32//! required other_field: str;
33//! }
34//! ```
35//!
36//! Here're some of the ways to use `EdgedbObject`:
37//!
38//! ```
39//! # tokio_test::block_on(async {
40//! use edgedb_composable_query::{query, EdgedbObject, EdgedbSetValue, Ref};
41//!
42//! #[derive(Debug, PartialEq, EdgedbObject)]
43//! struct AdHocStruct {
44//! a: String,
45//! b: Option<String>,
46//! }
47//!
48//! #[derive(Debug, PartialEq, EdgedbObject)]
49//! struct Inner {
50//! req: String,
51//! opt: Option<String>,
52//! }
53//!
54//!
55//! // typically you want to use Ref<T> to refer to a type
56//! // Ref<T> is basically UUID and an Option<T>
57//!
58//! #[derive(Debug, PartialEq, EdgedbObject)]
59//! struct Outer {
60//! inner: Option<Ref<Inner>>,
61//! some_field: Option<String>,
62//! other_field: String,
63//! }
64//!
65//! let conn = edgedb_tokio::create_client().await?;
66//!
67//! assert_eq!(query::<i64, _>(&conn, "select 7*8", ()).await?, 56);
68//!
69//! // use primitive params
70//! assert_eq!(
71//! query::<Vec<i64>, _>(&conn, "select {1 * <int32>$0, 2 * <int32>$0}", (22,)).await?,
72//! vec![22, 44]
73//! );
74//!
75//! // ad-hoc objects:
76//! assert_eq!(
77//! query::<AdHocStruct, _>(&conn, "select { a := 'aaa', b := <str>{} }", ()).await?,
78//! AdHocStruct {
79//! a: "aaa".to_string(),
80//! b: None
81//! }
82//! );
83//!
84//! assert_eq!(
85//! query::<AdHocStruct, _>(&conn, "select { a:= 'aaa', b:=<str>{'cc'} }", ()).await?,
86//! AdHocStruct {
87//! a: "aaa".to_string(),
88//! b: Some("cc".to_string())
89//! }
90//! );
91//!
92//! // cardinality mismatch:
93//! assert!(
94//! query::<AdHocStruct, _>(&conn, "select {a := 'aaa',b := <str>{'cc', 'dd'}}", ())
95//! .await
96//! .is_err()
97//! );
98//!
99//! // look up some objects
100//! assert!(
101//! dbg!(
102//! query::<Vec<Inner>, _>(&conn, "select Inner {req, opt}", ())
103//! .await?
104//! ).len()
105//! > 0
106//! );
107//!
108//! // use ref if you need ids
109//! assert!(
110//! dbg!(
111//! query::<Vec<Ref<Inner>>, _>(&conn, "select Inner {id, req, opt}", ())
112//! .await?
113//! ).len()
114//! > 0
115//! );
116//!
117//!
118//! // ref doesn't need the rest of the object
119//! assert!(
120//! dbg!(
121//! query::<Vec<Ref<Inner>>, _>(&conn, "select Inner {id}", ())
122//! .await?
123//! ).len()
124//! > 0
125//! );
126//!
127//! // cardinality mismatch:
128//! assert!(
129//! query::<Ref<Inner>, _>(&conn, "select Inner {id}", ())
130//! .await
131//! .is_err()
132//! );
133//!
134//! // you can query things with refs in them:
135//!
136//! let vs = query::<Vec<Outer>, _>(&conn, "select Outer {inner, some_field, other_field}", ())
137//! .await?;
138//!
139//! assert!(vs.len() > 0);
140//!
141//!
142//! // refs picked up only ids here
143//! assert!(
144//! vs.iter()
145//! .filter_map(|v| v.inner.as_ref())
146//! .all(|inner_ref| inner_ref.known_value.is_none())
147//! );
148//!
149//!
150//!
151//! // if you want the whole object with Ref, don't forget to provide 'id' selector
152//! let vs = query::<Vec<Outer>, _>(&conn, "
153//! select Outer {
154//! inner: { id, req, opt },
155//! some_field,
156//! other_field
157//! }
158//! ", ())
159//! .await?;
160//!
161//! assert!(vs.len() > 0);
162//!
163//! // refs picked up the whole objects
164//! assert!(
165//! vs.iter()
166//! .filter_map(|v| v.inner.as_ref())
167//! .all(|inner_ref| inner_ref.known_value.is_some())
168//! );
169//!
170//! # anyhow::Ok(())
171//! # }).unwrap();
172//! ```
173
174/// currently anyhow::Result. TODO: make crate's own Result type
175pub use anyhow::Result;
176
177#[doc(hidden)]
178pub use itertools as __itertools;
179
180#[doc(hidden)]
181pub fn __query_add_indent(s: &str) -> String {
182 s.replace('\n', "\n\t")
183}
184
185use edgedb_tokio::Client;
186
187mod args;
188pub use args::EdgedbQueryArgs;
189mod prim;
190pub use prim::{EdgedbJson, EdgedbPrim};
191
192/// Express complex, composable queries through Rust structs and attributes.
193pub mod composable;
194mod refs;
195mod tuples;
196mod value;
197
198extern crate self as edgedb_composable_query;
199
200pub use edgedb_composable_query_derive::EdgedbObject;
201pub use refs::Ref;
202
203use edgedb_protocol::{codec::ObjectShape, value::Value};
204
205pub use nonempty;
206
207pub use value::{EdgedbSetValue, EdgedbValue};
208
209/// Struct that can be received from EdgeDB as an Object. Derive this trait for your structs.
210pub trait EdgedbObject: Sized {
211 fn from_edgedb_object(shape: ObjectShape, fields: Vec<Option<Value>>) -> Result<Self>;
212 // fn to_edgedb_object(&self) -> Result<(ObjectShape, Vec<Option<Value>>)>;
213}
214
215/// Entry-point for querying `EdgedbObject`s of arbitrary cardinality.
216pub async fn query<T: EdgedbSetValue, Args: EdgedbQueryArgs + Send>(
217 client: &Client,
218 q: &str,
219 args: Args,
220) -> Result<T> {
221 let val = T::query_direct(client, q, args).await?;
222 Ok(val)
223}
224
225#[cfg(test)]
226mod test {
227 use crate::{query, EdgedbObject, EdgedbSetValue};
228
229 #[derive(Debug, PartialEq, EdgedbObject)]
230 struct ExamplImplStruct {
231 a: String,
232 b: Option<String>,
233 }
234
235 #[tokio::test]
236 async fn some_queries() -> anyhow::Result<()> {
237 let conn = edgedb_tokio::create_client().await?;
238
239 assert_eq!(query::<i64, _>(&conn, "select 7*8", ()).await?, 56);
240
241 assert_eq!(
242 query::<ExamplImplStruct, _>(&conn, "select {a:='aaa',b:=<str>{}}", ()).await?,
243 ExamplImplStruct {
244 a: "aaa".to_string(),
245 b: None
246 }
247 );
248
249 assert_eq!(
250 query::<ExamplImplStruct, _>(&conn, "select {a:='aaa',b:=<str>{'cc'}}", ()).await?,
251 ExamplImplStruct {
252 a: "aaa".to_string(),
253 b: Some("cc".to_string())
254 }
255 );
256
257 assert!(
258 query::<ExamplImplStruct, _>(&conn, "select {a:='aaa',b:=<str>{'cc', 'dd'}}", ())
259 .await
260 .is_err()
261 );
262
263 Ok(())
264 }
265}