1use crate::value::Value;
4use rkyv::{Archive, Deserialize, Serialize};
5use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
9pub enum AggregateFunction {
10 Count,
12 Sum,
14 Avg,
16 Min,
18 Max,
20}
21
22#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
24pub struct Aggregation {
25 pub function: AggregateFunction,
27 pub field: Option<String>,
29}
30
31impl Aggregation {
32 pub fn count() -> Self {
34 Self {
35 function: AggregateFunction::Count,
36 field: None,
37 }
38 }
39
40 pub fn count_field(field: impl Into<String>) -> Self {
42 Self {
43 function: AggregateFunction::Count,
44 field: Some(field.into()),
45 }
46 }
47
48 pub fn sum(field: impl Into<String>) -> Self {
50 Self {
51 function: AggregateFunction::Sum,
52 field: Some(field.into()),
53 }
54 }
55
56 pub fn avg(field: impl Into<String>) -> Self {
58 Self {
59 function: AggregateFunction::Avg,
60 field: Some(field.into()),
61 }
62 }
63
64 pub fn min(field: impl Into<String>) -> Self {
66 Self {
67 function: AggregateFunction::Min,
68 field: Some(field.into()),
69 }
70 }
71
72 pub fn max(field: impl Into<String>) -> Self {
74 Self {
75 function: AggregateFunction::Max,
76 field: Some(field.into()),
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
83pub struct AggregateQuery {
84 pub root_entity: String,
86 pub aggregations: Vec<Aggregation>,
88 pub filter: Option<Filter>,
90}
91
92impl AggregateQuery {
93 pub fn new(root_entity: impl Into<String>) -> Self {
95 Self {
96 root_entity: root_entity.into(),
97 aggregations: vec![],
98 filter: None,
99 }
100 }
101
102 pub fn with_aggregation(mut self, aggregation: Aggregation) -> Self {
104 self.aggregations.push(aggregation);
105 self
106 }
107
108 pub fn count(mut self) -> Self {
110 self.aggregations.push(Aggregation::count());
111 self
112 }
113
114 pub fn sum(mut self, field: impl Into<String>) -> Self {
116 self.aggregations.push(Aggregation::sum(field));
117 self
118 }
119
120 pub fn avg(mut self, field: impl Into<String>) -> Self {
122 self.aggregations.push(Aggregation::avg(field));
123 self
124 }
125
126 pub fn min(mut self, field: impl Into<String>) -> Self {
128 self.aggregations.push(Aggregation::min(field));
129 self
130 }
131
132 pub fn max(mut self, field: impl Into<String>) -> Self {
134 self.aggregations.push(Aggregation::max(field));
135 self
136 }
137
138 pub fn with_filter(mut self, filter: Filter) -> Self {
140 self.filter = Some(filter);
141 self
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
150pub struct GraphQuery {
151 pub root_entity: String,
153 pub fields: Vec<String>,
155 pub includes: Vec<RelationInclude>,
157 pub filter: Option<Filter>,
159 pub order_by: Vec<OrderSpec>,
161 pub pagination: Option<Pagination>,
163}
164
165#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
172pub struct RelationInclude {
173 pub path: String,
175 pub fields: Vec<String>,
177 pub filter: Option<Filter>,
179 pub order_by: Vec<OrderSpec>,
181 pub pagination: Option<Pagination>,
183}
184
185impl RelationInclude {
186 pub fn new(path: impl Into<String>) -> Self {
188 Self {
189 path: path.into(),
190 fields: vec![],
191 filter: None,
192 order_by: vec![],
193 pagination: None,
194 }
195 }
196
197 pub fn with_fields(mut self, fields: Vec<String>) -> Self {
199 self.fields = fields;
200 self
201 }
202
203 pub fn with_filter(mut self, filter: Filter) -> Self {
205 self.filter = Some(filter);
206 self
207 }
208
209 pub fn with_order(mut self, order: OrderSpec) -> Self {
211 self.order_by.push(order);
212 self
213 }
214
215 pub fn with_pagination(mut self, pagination: Pagination) -> Self {
217 self.pagination = Some(pagination);
218 self
219 }
220
221 pub fn relation_name(&self) -> &str {
223 self.path.rsplit('.').next().unwrap_or(&self.path)
224 }
225
226 pub fn parent_path(&self) -> Option<&str> {
228 self.path.rsplit_once('.').map(|(parent, _)| parent)
229 }
230
231 pub fn is_top_level(&self) -> bool {
233 !self.path.contains('.')
234 }
235
236 pub fn depth(&self) -> usize {
238 self.path.matches('.').count() + 1
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
244pub struct Filter {
245 pub expression: FilterExpr,
247}
248
249impl Filter {
250 pub fn new(expression: FilterExpr) -> Self {
252 Self { expression }
253 }
254}
255
256impl From<FilterExpr> for Filter {
257 fn from(expression: FilterExpr) -> Self {
258 Self { expression }
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
267pub enum FilterExpr {
268 Eq { field: String, value: Value },
270 Ne { field: String, value: Value },
272 Lt { field: String, value: Value },
274 Le { field: String, value: Value },
276 Gt { field: String, value: Value },
278 Ge { field: String, value: Value },
280 In { field: String, values: Vec<Value> },
282 NotIn { field: String, values: Vec<Value> },
284 IsNull { field: String },
286 IsNotNull { field: String },
288 Like { field: String, pattern: String },
290 NotLike { field: String, pattern: String },
292 And(Vec<SimpleFilter>),
294 Or(Vec<SimpleFilter>),
296}
297
298#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
302pub enum SimpleFilter {
303 Eq { field: String, value: Value },
305 Ne { field: String, value: Value },
307 Lt { field: String, value: Value },
309 Le { field: String, value: Value },
311 Gt { field: String, value: Value },
313 Ge { field: String, value: Value },
315 In { field: String, values: Vec<Value> },
317 NotIn { field: String, values: Vec<Value> },
319 IsNull { field: String },
321 IsNotNull { field: String },
323 Like { field: String, pattern: String },
325 NotLike { field: String, pattern: String },
327}
328
329impl SimpleFilter {
330 pub fn eq(field: impl Into<String>, value: impl Into<Value>) -> Self {
332 SimpleFilter::Eq {
333 field: field.into(),
334 value: value.into(),
335 }
336 }
337
338 pub fn ne(field: impl Into<String>, value: impl Into<Value>) -> Self {
340 SimpleFilter::Ne {
341 field: field.into(),
342 value: value.into(),
343 }
344 }
345
346 pub fn is_null(field: impl Into<String>) -> Self {
348 SimpleFilter::IsNull {
349 field: field.into(),
350 }
351 }
352
353 pub fn is_not_null(field: impl Into<String>) -> Self {
355 SimpleFilter::IsNotNull {
356 field: field.into(),
357 }
358 }
359}
360
361impl FilterExpr {
362 pub fn eq(field: impl Into<String>, value: impl Into<Value>) -> Self {
364 FilterExpr::Eq {
365 field: field.into(),
366 value: value.into(),
367 }
368 }
369
370 pub fn ne(field: impl Into<String>, value: impl Into<Value>) -> Self {
372 FilterExpr::Ne {
373 field: field.into(),
374 value: value.into(),
375 }
376 }
377
378 pub fn lt(field: impl Into<String>, value: impl Into<Value>) -> Self {
380 FilterExpr::Lt {
381 field: field.into(),
382 value: value.into(),
383 }
384 }
385
386 pub fn le(field: impl Into<String>, value: impl Into<Value>) -> Self {
388 FilterExpr::Le {
389 field: field.into(),
390 value: value.into(),
391 }
392 }
393
394 pub fn gt(field: impl Into<String>, value: impl Into<Value>) -> Self {
396 FilterExpr::Gt {
397 field: field.into(),
398 value: value.into(),
399 }
400 }
401
402 pub fn ge(field: impl Into<String>, value: impl Into<Value>) -> Self {
404 FilterExpr::Ge {
405 field: field.into(),
406 value: value.into(),
407 }
408 }
409
410 pub fn in_values(field: impl Into<String>, values: Vec<Value>) -> Self {
412 FilterExpr::In {
413 field: field.into(),
414 values,
415 }
416 }
417
418 pub fn not_in_values(field: impl Into<String>, values: Vec<Value>) -> Self {
420 FilterExpr::NotIn {
421 field: field.into(),
422 values,
423 }
424 }
425
426 pub fn is_null(field: impl Into<String>) -> Self {
428 FilterExpr::IsNull {
429 field: field.into(),
430 }
431 }
432
433 pub fn is_not_null(field: impl Into<String>) -> Self {
435 FilterExpr::IsNotNull {
436 field: field.into(),
437 }
438 }
439
440 pub fn like(field: impl Into<String>, pattern: impl Into<String>) -> Self {
442 FilterExpr::Like {
443 field: field.into(),
444 pattern: pattern.into(),
445 }
446 }
447
448 pub fn and(exprs: Vec<SimpleFilter>) -> Self {
450 FilterExpr::And(exprs)
451 }
452
453 pub fn or(exprs: Vec<SimpleFilter>) -> Self {
455 FilterExpr::Or(exprs)
456 }
457}
458
459#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
461pub struct OrderSpec {
462 pub field: String,
464 pub direction: OrderDirection,
466}
467
468impl OrderSpec {
469 pub fn asc(field: impl Into<String>) -> Self {
471 Self {
472 field: field.into(),
473 direction: OrderDirection::Asc,
474 }
475 }
476
477 pub fn desc(field: impl Into<String>) -> Self {
479 Self {
480 field: field.into(),
481 direction: OrderDirection::Desc,
482 }
483 }
484}
485
486#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
488pub enum OrderDirection {
489 Asc,
491 Desc,
493}
494
495#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
497pub struct Pagination {
498 pub limit: u32,
500 pub offset: u32,
502 pub cursor: Option<Vec<u8>>,
504}
505
506impl Pagination {
507 pub fn new(limit: u32, offset: u32) -> Self {
509 Self {
510 limit,
511 offset,
512 cursor: None,
513 }
514 }
515
516 pub fn limit(limit: u32) -> Self {
518 Self {
519 limit,
520 offset: 0,
521 cursor: None,
522 }
523 }
524
525 pub fn cursor(cursor: Vec<u8>, limit: u32) -> Self {
527 Self {
528 limit,
529 offset: 0,
530 cursor: Some(cursor),
531 }
532 }
533}
534
535impl GraphQuery {
536 pub fn new(root_entity: impl Into<String>) -> Self {
538 Self {
539 root_entity: root_entity.into(),
540 fields: vec![],
541 includes: vec![],
542 filter: None,
543 order_by: vec![],
544 pagination: None,
545 }
546 }
547
548 pub fn with_fields(mut self, fields: Vec<String>) -> Self {
550 self.fields = fields;
551 self
552 }
553
554 pub fn select(mut self, field: impl Into<String>) -> Self {
556 self.fields.push(field.into());
557 self
558 }
559
560 pub fn include(mut self, include: RelationInclude) -> Self {
562 self.includes.push(include);
563 self
564 }
565
566 pub fn with_filter(mut self, filter: Filter) -> Self {
568 self.filter = Some(filter);
569 self
570 }
571
572 pub fn with_order(mut self, order: OrderSpec) -> Self {
574 self.order_by.push(order);
575 self
576 }
577
578 pub fn with_pagination(mut self, pagination: Pagination) -> Self {
580 self.pagination = Some(pagination);
581 self
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn test_simple_query() {
591 let query = GraphQuery::new("User")
592 .with_fields(vec!["id".into(), "name".into(), "email".into()])
593 .with_filter(FilterExpr::eq("active", true).into())
594 .with_order(OrderSpec::asc("name"))
595 .with_pagination(Pagination::limit(10));
596
597 assert_eq!(query.root_entity, "User");
598 assert_eq!(query.fields.len(), 3);
599 assert!(query.filter.is_some());
600 assert_eq!(query.order_by.len(), 1);
601 assert!(query.pagination.is_some());
602 }
603
604 #[test]
605 fn test_nested_query_with_path_notation() {
606 let query = GraphQuery::new("User")
607 .with_fields(vec!["id".into(), "name".into()])
608 .include(RelationInclude::new("posts").with_fields(vec!["id".into(), "title".into()]))
609 .include(
610 RelationInclude::new("posts.comments")
611 .with_filter(FilterExpr::is_not_null("content").into()),
612 )
613 .include(RelationInclude::new("posts.author"))
614 .with_filter(FilterExpr::eq("id", Value::Uuid([1; 16])).into());
615
616 assert_eq!(query.includes.len(), 3);
617 assert_eq!(query.includes[0].path, "posts");
618 assert!(query.includes[0].is_top_level());
619 assert_eq!(query.includes[1].path, "posts.comments");
620 assert!(!query.includes[1].is_top_level());
621 assert_eq!(query.includes[1].parent_path(), Some("posts"));
622 assert_eq!(query.includes[1].depth(), 2);
623 }
624
625 #[test]
626 fn test_relation_include_helpers() {
627 let include = RelationInclude::new("posts.comments.likes");
628 assert_eq!(include.relation_name(), "likes");
629 assert_eq!(include.parent_path(), Some("posts.comments"));
630 assert_eq!(include.depth(), 3);
631 assert!(!include.is_top_level());
632
633 let top_level = RelationInclude::new("posts");
634 assert_eq!(top_level.relation_name(), "posts");
635 assert_eq!(top_level.parent_path(), None);
636 assert_eq!(top_level.depth(), 1);
637 assert!(top_level.is_top_level());
638 }
639
640 #[test]
641 fn test_complex_filter() {
642 let filter = FilterExpr::and(vec![
643 SimpleFilter::eq("status", "active"),
644 SimpleFilter::is_not_null("email"),
645 ]);
646
647 if let FilterExpr::And(exprs) = &filter {
648 assert_eq!(exprs.len(), 2);
649 } else {
650 panic!("Expected And filter");
651 }
652 }
653
654 #[test]
655 fn test_query_serialization_roundtrip() {
656 let query = GraphQuery::new("Post")
657 .with_fields(vec!["id".into(), "title".into(), "content".into()])
658 .include(RelationInclude::new("author"))
659 .include(RelationInclude::new("comments").with_pagination(Pagination::limit(10)))
660 .with_filter(
661 FilterExpr::and(vec![
662 SimpleFilter::eq("published", true),
663 ])
664 .into(),
665 )
666 .with_order(OrderSpec::desc("created_at"))
667 .with_pagination(Pagination::new(20, 0));
668
669 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&query).unwrap();
670 let archived = rkyv::access::<ArchivedGraphQuery, rkyv::rancor::Error>(&bytes).unwrap();
671 let deserialized: GraphQuery =
672 rkyv::deserialize::<GraphQuery, rkyv::rancor::Error>(archived).unwrap();
673
674 assert_eq!(query, deserialized);
675 }
676}