1use std::marker::PhantomData;
6
7use chrono::NaiveDate;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct QueryDecl {
12 pub name: String,
13 #[serde(default, skip_serializing_if = "String::is_empty")]
14 pub doc: String,
15 pub bindings: Vec<BindingDecl>,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub filter: Option<BoolExpr>,
18 #[serde(default, skip_serializing_if = "Vec::is_empty")]
19 pub group_by: Vec<ScalarExpr>,
20 pub columns: Vec<OutputColumn>,
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub order_by: Vec<QueryOrder>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub limit: Option<usize>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct BindingDecl {
29 pub id: u32,
30 pub kind: String,
31 pub source: BindingSource,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub enum BindingSource {
36 Root,
37 Follow {
38 from: u32,
39 field: String,
40 direction: EdgeDirection,
41 #[serde(default = "yes")]
42 required: bool,
43 },
44}
45
46fn yes() -> bool {
47 true
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum EdgeDirection {
52 Out,
53 In,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct FieldRef {
58 pub binding: u32,
59 pub field: String,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub enum ScalarExpr {
64 Field(FieldRef),
65 Literal(TypedLiteral),
66 Aggregate {
67 op: AggregateOp,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 expr: Option<Box<ScalarExpr>>,
70 },
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub enum TypedLiteral {
75 String(String),
76 Int(i64),
77 Decimal(String),
78 Date(NaiveDate),
79 Bool(bool),
80 Enum { type_name: String, variant: String },
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84pub enum AggregateOp {
85 Count,
86 CountDistinct,
87 Sum,
88 Min,
89 Max,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub enum BoolExpr {
94 And(Vec<BoolExpr>),
95 Or(Vec<BoolExpr>),
96 Not(Box<BoolExpr>),
97 Compare {
98 left: ScalarExpr,
99 op: CompareOp,
100 right: ScalarExpr,
101 },
102}
103
104impl BoolExpr {
105 pub fn and(self, other: BoolExpr) -> BoolExpr {
106 match self {
107 BoolExpr::And(mut xs) => {
108 xs.push(other);
109 BoolExpr::And(xs)
110 }
111 one => BoolExpr::And(vec![one, other]),
112 }
113 }
114
115 pub fn or(self, other: BoolExpr) -> BoolExpr {
116 match self {
117 BoolExpr::Or(mut xs) => {
118 xs.push(other);
119 BoolExpr::Or(xs)
120 }
121 one => BoolExpr::Or(vec![one, other]),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127pub enum CompareOp {
128 Eq,
129 Ne,
130 Gt,
131 Gte,
132 Lt,
133 Lte,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct OutputColumn {
138 pub name: String,
139 pub expr: ScalarExpr,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub format: Option<ColumnFormat>,
144}
145
146#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
150pub struct ColumnFormat {
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub prefix: Option<String>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub suffix: Option<String>,
155 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
156 pub thousands: bool,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub decimals: Option<u8>,
160}
161
162impl ColumnFormat {
163 pub fn money(prefix: impl Into<String>) -> ColumnFormat {
165 ColumnFormat {
166 prefix: Some(prefix.into()),
167 suffix: None,
168 thousands: true,
169 decimals: Some(2),
170 }
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct QueryOrder {
176 pub expr: ScalarExpr,
177 pub direction: SortDirection,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
181pub enum SortDirection {
182 Asc,
183 Desc,
184}
185
186pub trait QueryModel: Sized {
189 type Fields;
190 const KIND: &'static str;
191 fn fields(binding: u32) -> Self::Fields;
192}
193
194#[derive(Debug)]
197pub struct FieldExpr<T> {
198 field: FieldRef,
199 marker: PhantomData<fn() -> T>,
200}
201
202impl<T> Clone for FieldExpr<T> {
203 fn clone(&self) -> Self {
204 Self {
205 field: self.field.clone(),
206 marker: PhantomData,
207 }
208 }
209}
210
211impl<T> FieldExpr<T> {
212 pub fn new(binding: u32, field: impl Into<String>) -> Self {
213 Self {
214 field: FieldRef {
215 binding,
216 field: field.into(),
217 },
218 marker: PhantomData,
219 }
220 }
221
222 pub fn expr(&self) -> ScalarExpr {
223 ScalarExpr::Field(self.field.clone())
224 }
225
226 fn compare(&self, op: CompareOp, value: T) -> BoolExpr
227 where
228 T: QueryLiteral,
229 {
230 BoolExpr::Compare {
231 left: self.expr(),
232 op,
233 right: ScalarExpr::Literal(value.into_query_literal()),
234 }
235 }
236
237 pub fn eq(&self, value: T) -> BoolExpr
238 where
239 T: QueryLiteral,
240 {
241 self.compare(CompareOp::Eq, value)
242 }
243
244 pub fn ne(&self, value: T) -> BoolExpr
245 where
246 T: QueryLiteral,
247 {
248 self.compare(CompareOp::Ne, value)
249 }
250
251 pub fn gt(&self, value: T) -> BoolExpr
252 where
253 T: QueryLiteral,
254 {
255 self.compare(CompareOp::Gt, value)
256 }
257
258 pub fn gte(&self, value: T) -> BoolExpr
259 where
260 T: QueryLiteral,
261 {
262 self.compare(CompareOp::Gte, value)
263 }
264
265 pub fn lt(&self, value: T) -> BoolExpr
266 where
267 T: QueryLiteral,
268 {
269 self.compare(CompareOp::Lt, value)
270 }
271
272 pub fn lte(&self, value: T) -> BoolExpr
273 where
274 T: QueryLiteral,
275 {
276 self.compare(CompareOp::Lte, value)
277 }
278
279 pub fn binding(&self) -> u32 {
282 self.field.binding
283 }
284}
285
286pub trait QueryLiteral {
287 fn into_query_literal(self) -> TypedLiteral;
288}
289
290impl QueryLiteral for String {
291 fn into_query_literal(self) -> TypedLiteral {
292 TypedLiteral::String(self)
293 }
294}
295impl QueryLiteral for &str {
296 fn into_query_literal(self) -> TypedLiteral {
297 TypedLiteral::String(self.to_string())
298 }
299}
300impl QueryLiteral for i64 {
301 fn into_query_literal(self) -> TypedLiteral {
302 TypedLiteral::Int(self)
303 }
304}
305impl QueryLiteral for bool {
306 fn into_query_literal(self) -> TypedLiteral {
307 TypedLiteral::Bool(self)
308 }
309}
310impl QueryLiteral for NaiveDate {
311 fn into_query_literal(self) -> TypedLiteral {
312 TypedLiteral::Date(self)
313 }
314}
315
316pub struct QueryBuilder {
319 query: QueryDecl,
320 next_binding: u32,
321}
322
323impl QueryBuilder {
324 pub fn new(name: impl Into<String>, doc: impl Into<String>) -> Self {
325 Self {
326 query: QueryDecl {
327 name: name.into(),
328 doc: doc.into(),
329 bindings: vec![],
330 filter: None,
331 group_by: vec![],
332 columns: vec![],
333 order_by: vec![],
334 limit: None,
335 },
336 next_binding: 0,
337 }
338 }
339
340 pub fn from<M: QueryModel>(&mut self) -> M::Fields {
341 let id = self.next_binding;
342 self.next_binding += 1;
343 self.query.bindings.push(BindingDecl {
344 id,
345 kind: M::KIND.into(),
346 source: BindingSource::Root,
347 });
348 M::fields(id)
349 }
350
351 pub fn filter(&mut self, expr: BoolExpr) {
352 self.query.filter = Some(expr);
353 }
354
355 pub fn group_by<T>(&mut self, field: &FieldExpr<T>) {
356 self.query.group_by.push(field.expr());
357 }
358
359 pub fn field<T>(&mut self, name: impl Into<String>, field: &FieldExpr<T>) -> ColumnHandle<'_> {
360 self.push_column(name, field.expr())
361 }
362
363 fn push_column(&mut self, name: impl Into<String>, expr: ScalarExpr) -> ColumnHandle<'_> {
364 self.query.columns.push(OutputColumn {
365 name: name.into(),
366 expr,
367 format: None,
368 });
369 ColumnHandle {
370 column: self.query.columns.last_mut().expect("just pushed"),
371 }
372 }
373
374 pub fn count(&mut self, name: impl Into<String>) -> ColumnHandle<'_> {
375 self.push_column(
376 name,
377 ScalarExpr::Aggregate {
378 op: AggregateOp::Count,
379 expr: None,
380 },
381 )
382 }
383
384 pub fn count_of<T>(
387 &mut self,
388 name: impl Into<String>,
389 field: &FieldExpr<T>,
390 ) -> ColumnHandle<'_> {
391 self.aggregate(name, AggregateOp::Count, field)
392 }
393
394 pub fn count_distinct<T>(
395 &mut self,
396 name: impl Into<String>,
397 field: &FieldExpr<T>,
398 ) -> ColumnHandle<'_> {
399 self.aggregate(name, AggregateOp::CountDistinct, field)
400 }
401
402 pub fn sum<T>(&mut self, name: impl Into<String>, field: &FieldExpr<T>) -> ColumnHandle<'_> {
403 self.aggregate(name, AggregateOp::Sum, field)
404 }
405
406 pub fn min<T>(&mut self, name: impl Into<String>, field: &FieldExpr<T>) -> ColumnHandle<'_> {
407 self.aggregate(name, AggregateOp::Min, field)
408 }
409
410 pub fn max<T>(&mut self, name: impl Into<String>, field: &FieldExpr<T>) -> ColumnHandle<'_> {
411 self.aggregate(name, AggregateOp::Max, field)
412 }
413
414 fn aggregate<T>(
415 &mut self,
416 name: impl Into<String>,
417 op: AggregateOp,
418 field: &FieldExpr<T>,
419 ) -> ColumnHandle<'_> {
420 self.push_column(
421 name,
422 ScalarExpr::Aggregate {
423 op,
424 expr: Some(Box::new(field.expr())),
425 },
426 )
427 }
428
429 pub fn follow_in<M: QueryModel>(
435 &mut self,
436 anchor: u32,
437 field: impl Into<String>,
438 required: bool,
439 ) -> M::Fields {
440 self.follow::<M>(anchor, field, EdgeDirection::In, required)
441 }
442
443 pub fn follow_out<M: QueryModel>(
445 &mut self,
446 anchor: u32,
447 field: impl Into<String>,
448 required: bool,
449 ) -> M::Fields {
450 self.follow::<M>(anchor, field, EdgeDirection::Out, required)
451 }
452
453 fn follow<M: QueryModel>(
454 &mut self,
455 anchor: u32,
456 field: impl Into<String>,
457 direction: EdgeDirection,
458 required: bool,
459 ) -> M::Fields {
460 let id = self.next_binding;
461 self.next_binding += 1;
462 self.query.bindings.push(BindingDecl {
463 id,
464 kind: M::KIND.into(),
465 source: BindingSource::Follow {
466 from: anchor,
467 field: field.into(),
468 direction,
469 required,
470 },
471 });
472 M::fields(id)
473 }
474
475 pub fn order_by<T>(&mut self, field: &FieldExpr<T>, direction: SortDirection) {
476 self.query.order_by.push(QueryOrder {
477 expr: field.expr(),
478 direction,
479 });
480 }
481
482 pub fn order_by_column(&mut self, name: &str, direction: SortDirection) {
484 if let Some(col) = self.query.columns.iter().find(|c| c.name == name) {
485 self.query.order_by.push(QueryOrder {
486 expr: col.expr.clone(),
487 direction,
488 });
489 }
490 }
491
492 pub fn limit(&mut self, limit: usize) {
493 self.query.limit = Some(limit);
494 }
495
496 pub fn finish(self) -> QueryDecl {
497 self.query
498 }
499}
500
501pub struct ColumnHandle<'a> {
504 column: &'a mut OutputColumn,
505}
506
507impl ColumnHandle<'_> {
508 pub fn format(self, format: ColumnFormat) -> Self {
509 self.column.format = Some(format);
510 self
511 }
512
513 pub fn money(self, prefix: impl Into<String>) -> Self {
514 self.format(ColumnFormat::money(prefix))
515 }
516}
517
518pub fn query(
519 name: impl Into<String>,
520 doc: impl Into<String>,
521 build: impl FnOnce(&mut QueryBuilder),
522) -> QueryDecl {
523 let mut q = QueryBuilder::new(name, doc);
524 build(&mut q);
525 q.finish()
526}