1use std::collections::BTreeMap;
4
5use serde_json::{json, Map, Number, Value};
6
7use crate::source::SourceSpan;
8
9pub const QUERY_PLAN_VERSION: u8 = 1;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum QueryOperation {
15 Select,
17 Insert,
19 Update,
21 Delete,
23}
24
25impl QueryOperation {
26 #[must_use]
28 pub const fn as_str(self) -> &'static str {
29 match self {
30 Self::Select => "select",
31 Self::Insert => "insert",
32 Self::Update => "update",
33 Self::Delete => "delete",
34 }
35 }
36
37 #[must_use]
39 pub fn parse(value: &str) -> Option<Self> {
40 match value {
41 "select" => Some(Self::Select),
42 "insert" => Some(Self::Insert),
43 "update" => Some(Self::Update),
44 "delete" => Some(Self::Delete),
45 _ => None,
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum QueryComparisonOperator {
53 Equal,
55 NotEqual,
57 LessThan,
59 LessThanOrEqual,
61 GreaterThan,
63 GreaterThanOrEqual,
65 In,
67 NotIn,
69 Like,
71 IsNull,
73}
74
75impl QueryComparisonOperator {
76 #[must_use]
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::Equal => "eq",
81 Self::NotEqual => "neq",
82 Self::LessThan => "lt",
83 Self::LessThanOrEqual => "lte",
84 Self::GreaterThan => "gt",
85 Self::GreaterThanOrEqual => "gte",
86 Self::In => "in",
87 Self::NotIn => "not-in",
88 Self::Like => "like",
89 Self::IsNull => "is-null",
90 }
91 }
92
93 #[must_use]
95 pub fn from_graphql_key(value: &str) -> Option<Self> {
96 match value {
97 "eq" => Some(Self::Equal),
98 "neq" | "ne" => Some(Self::NotEqual),
99 "lt" => Some(Self::LessThan),
100 "lte" => Some(Self::LessThanOrEqual),
101 "gt" => Some(Self::GreaterThan),
102 "gte" => Some(Self::GreaterThanOrEqual),
103 "in" => Some(Self::In),
104 "notIn" => Some(Self::NotIn),
105 "like" => Some(Self::Like),
106 "isNull" => Some(Self::IsNull),
107 _ => None,
108 }
109 }
110}
111
112#[derive(Clone, Debug, PartialEq)]
114pub enum QueryValue {
115 Null,
117 Boolean(bool),
119 Integer(i64),
121 Float(f64),
123 String(String),
126 List(Vec<Self>),
128 Object(BTreeMap<String, Self>),
130}
131
132impl QueryValue {
133 #[must_use]
135 pub fn to_json(&self) -> Value {
136 match self {
137 Self::Null => Value::Null,
138 Self::Boolean(value) => Value::Bool(*value),
139 Self::Integer(value) => Value::Number(Number::from(*value)),
140 Self::Float(value) => Value::Number(
141 Number::from_f64(*value).expect("query plan floating-point values must be finite"),
142 ),
143 Self::String(value) => Value::String(value.clone()),
144 Self::List(values) => Value::Array(values.iter().map(Self::to_json).collect()),
145 Self::Object(values) => Value::Object(
146 values
147 .iter()
148 .map(|(key, value)| (key.clone(), value.to_json()))
149 .collect(),
150 ),
151 }
152 }
153}
154
155#[derive(Clone, Debug, PartialEq)]
157pub enum QueryFilter {
158 Compare {
160 field: String,
162 operator: QueryComparisonOperator,
164 value: QueryValue,
166 },
167 And(Vec<Self>),
169 Or(Vec<Self>),
171 Not(Box<Self>),
173}
174
175impl QueryFilter {
176 fn to_json(&self) -> Value {
177 match self {
178 Self::Compare {
179 field,
180 operator,
181 value,
182 } => json!({
183 "compare": {
184 "field": field,
185 "operator": operator.as_str(),
186 "value": value.to_json(),
187 }
188 }),
189 Self::And(children) => {
190 json!({"and": children.iter().map(Self::to_json).collect::<Vec<_>>()})
191 }
192 Self::Or(children) => {
193 json!({"or": children.iter().map(Self::to_json).collect::<Vec<_>>()})
194 }
195 Self::Not(child) => json!({"not": child.to_json()}),
196 }
197 }
198}
199
200#[derive(Clone, Copy, Debug, PartialEq, Eq)]
202pub enum QuerySortDirection {
203 Ascending,
205 Descending,
207}
208
209impl QuerySortDirection {
210 #[must_use]
212 pub const fn as_str(self) -> &'static str {
213 match self {
214 Self::Ascending => "asc",
215 Self::Descending => "desc",
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
222pub struct QueryOrder {
223 pub(crate) field: String,
224 pub(crate) direction: QuerySortDirection,
225}
226
227impl QueryOrder {
228 #[must_use]
230 pub fn new(field: impl Into<String>, direction: QuerySortDirection) -> Self {
231 Self {
232 field: field.into(),
233 direction,
234 }
235 }
236
237 #[must_use]
239 pub fn field(&self) -> &str {
240 &self.field
241 }
242
243 #[must_use]
245 pub const fn direction(&self) -> QuerySortDirection {
246 self.direction
247 }
248}
249
250#[derive(Clone, Copy, Debug, PartialEq, Eq)]
252pub enum QueryAggregateFunction {
253 Count,
255 Sum,
257 Average,
259 Minimum,
261 Maximum,
263 PopulationVariance,
265 PopulationStandardDeviation,
267}
268
269impl QueryAggregateFunction {
270 #[must_use]
272 pub const fn as_str(self) -> &'static str {
273 match self {
274 Self::Count => "count",
275 Self::Sum => "sum",
276 Self::Average => "avg",
277 Self::Minimum => "min",
278 Self::Maximum => "max",
279 Self::PopulationVariance => "variance-population",
280 Self::PopulationStandardDeviation => "stddev-population",
281 }
282 }
283
284 #[must_use]
286 pub fn parse(value: &str) -> Option<Self> {
287 match value {
288 "count" => Some(Self::Count),
289 "sum" => Some(Self::Sum),
290 "avg" => Some(Self::Average),
291 "min" => Some(Self::Minimum),
292 "max" => Some(Self::Maximum),
293 "variance-population" => Some(Self::PopulationVariance),
294 "stddev-population" => Some(Self::PopulationStandardDeviation),
295 _ => None,
296 }
297 }
298}
299
300#[derive(Clone, Debug, PartialEq, Eq)]
302pub struct QueryAggregate {
303 pub(crate) function: QueryAggregateFunction,
304 pub(crate) field: Option<String>,
305 pub(crate) alias: Option<String>,
306}
307
308impl QueryAggregate {
309 #[must_use]
311 pub const fn new(
312 function: QueryAggregateFunction,
313 field: Option<String>,
314 alias: Option<String>,
315 ) -> Self {
316 Self {
317 function,
318 field,
319 alias,
320 }
321 }
322
323 #[must_use]
325 pub const fn function(&self) -> QueryAggregateFunction {
326 self.function
327 }
328
329 #[must_use]
331 pub fn field(&self) -> Option<&str> {
332 self.field.as_deref()
333 }
334
335 #[must_use]
337 pub fn alias(&self) -> Option<&str> {
338 self.alias.as_deref()
339 }
340}
341
342#[derive(Clone, Debug, PartialEq, Eq)]
344pub struct QuerySourceEvidence {
345 role: String,
346 span: SourceSpan,
347}
348
349impl QuerySourceEvidence {
350 #[must_use]
352 pub fn new(role: impl Into<String>, span: SourceSpan) -> Self {
353 Self {
354 role: role.into(),
355 span,
356 }
357 }
358
359 #[must_use]
361 pub fn role(&self) -> &str {
362 &self.role
363 }
364
365 #[must_use]
367 pub const fn span(&self) -> SourceSpan {
368 self.span
369 }
370}
371
372#[derive(Clone, Debug, PartialEq)]
374pub struct QueryPlan {
375 pub(crate) operation: QueryOperation,
376 pub(crate) resource: String,
377 pub(crate) projection: Vec<String>,
378 pub(crate) filter: Option<QueryFilter>,
379 pub(crate) order: Vec<QueryOrder>,
380 pub(crate) limit: Option<u64>,
381 pub(crate) offset: Option<u64>,
382 pub(crate) group_by: Vec<String>,
383 pub(crate) aggregates: Vec<QueryAggregate>,
384 pub(crate) mutation: BTreeMap<String, QueryValue>,
385 pub(crate) source_evidence: Vec<QuerySourceEvidence>,
386}
387
388impl QueryPlan {
389 #[must_use]
391 pub fn new(operation: QueryOperation, resource: impl Into<String>) -> Self {
392 Self {
393 operation,
394 resource: resource.into(),
395 projection: Vec::new(),
396 filter: None,
397 order: Vec::new(),
398 limit: None,
399 offset: None,
400 group_by: Vec::new(),
401 aggregates: Vec::new(),
402 mutation: BTreeMap::new(),
403 source_evidence: Vec::new(),
404 }
405 }
406
407 pub fn add_projection(&mut self, field: impl Into<String>) {
409 self.projection.push(field.into());
410 }
411
412 pub fn set_filter(&mut self, filter: QueryFilter) {
414 self.filter = Some(filter);
415 }
416
417 pub fn add_order(&mut self, order: QueryOrder) {
419 self.order.push(order);
420 }
421
422 pub const fn set_pagination(&mut self, limit: Option<u64>, offset: Option<u64>) {
424 self.limit = limit;
425 self.offset = offset;
426 }
427
428 pub fn add_group_by(&mut self, field: impl Into<String>) {
430 self.group_by.push(field.into());
431 }
432
433 pub fn add_aggregate(&mut self, aggregate: QueryAggregate) {
435 self.aggregates.push(aggregate);
436 }
437
438 pub fn set_mutation_value(&mut self, field: impl Into<String>, value: QueryValue) {
440 self.mutation.insert(field.into(), value);
441 }
442
443 pub fn add_source_evidence(&mut self, evidence: QuerySourceEvidence) {
445 self.source_evidence.push(evidence);
446 }
447
448 #[must_use]
450 pub const fn operation(&self) -> QueryOperation {
451 self.operation
452 }
453
454 #[must_use]
456 pub fn resource(&self) -> &str {
457 &self.resource
458 }
459
460 #[must_use]
462 pub fn projection(&self) -> &[String] {
463 &self.projection
464 }
465
466 #[must_use]
468 pub const fn filter(&self) -> Option<&QueryFilter> {
469 self.filter.as_ref()
470 }
471
472 #[must_use]
474 pub fn order(&self) -> &[QueryOrder] {
475 &self.order
476 }
477
478 #[must_use]
480 pub const fn limit(&self) -> Option<u64> {
481 self.limit
482 }
483
484 #[must_use]
486 pub const fn offset(&self) -> Option<u64> {
487 self.offset
488 }
489
490 #[must_use]
492 pub fn group_by(&self) -> &[String] {
493 &self.group_by
494 }
495
496 #[must_use]
498 pub fn aggregates(&self) -> &[QueryAggregate] {
499 &self.aggregates
500 }
501
502 #[must_use]
504 pub const fn mutation(&self) -> &BTreeMap<String, QueryValue> {
505 &self.mutation
506 }
507
508 #[must_use]
510 pub fn source_evidence(&self) -> &[QuerySourceEvidence] {
511 &self.source_evidence
512 }
513
514 #[must_use]
517 pub fn canonical_value(&self) -> Value {
518 let order = self
519 .order
520 .iter()
521 .map(|entry| {
522 json!({
523 "field": entry.field,
524 "direction": entry.direction.as_str(),
525 })
526 })
527 .collect::<Vec<_>>();
528 let aggregates = self
529 .aggregates
530 .iter()
531 .map(|aggregate| {
532 json!({
533 "function": aggregate.function.as_str(),
534 "field": aggregate.field,
535 "alias": aggregate.alias,
536 })
537 })
538 .collect::<Vec<_>>();
539 let mutation = self
540 .mutation
541 .iter()
542 .map(|(field, value)| (field.clone(), value.to_json()))
543 .collect::<Map<_, _>>();
544
545 json!({
546 "version": QUERY_PLAN_VERSION,
547 "operation": self.operation.as_str(),
548 "resource": self.resource,
549 "projection": self.projection,
550 "filter": self.filter.as_ref().map(QueryFilter::to_json),
551 "order": order,
552 "limit": self.limit,
553 "offset": self.offset,
554 "groupBy": self.group_by,
555 "aggregates": aggregates,
556 "mutation": mutation,
557 })
558 }
559
560 #[must_use]
563 pub fn canonical_json(&self) -> String {
564 serde_json::to_string(&self.canonical_value()).expect("query plan values are serializable")
565 }
566}