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}