1use std::collections::BTreeMap;
4
5use serde_json::{json, Map, Number, Value};
6
7use crate::link_network::{Link, LinkId, LinkMetadata, LinkNetwork, LinkType};
8use crate::source::SourceSpan;
9
10pub const QUERY_PLAN_VERSION: u8 = 1;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum QueryAuthorization {
18 Required,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum QueryOperation {
25 Select,
27 Insert,
29 Update,
31 Delete,
33}
34
35impl QueryOperation {
36 #[must_use]
38 pub const fn as_str(self) -> &'static str {
39 match self {
40 Self::Select => "select",
41 Self::Insert => "insert",
42 Self::Update => "update",
43 Self::Delete => "delete",
44 }
45 }
46
47 #[must_use]
49 pub fn parse(value: &str) -> Option<Self> {
50 match value {
51 "select" => Some(Self::Select),
52 "insert" => Some(Self::Insert),
53 "update" => Some(Self::Update),
54 "delete" => Some(Self::Delete),
55 _ => None,
56 }
57 }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum QueryComparisonOperator {
63 Equal,
65 NotEqual,
67 LessThan,
69 LessThanOrEqual,
71 GreaterThan,
73 GreaterThanOrEqual,
75 In,
77 NotIn,
79 Like,
81 IsNull,
83}
84
85impl QueryComparisonOperator {
86 #[must_use]
88 pub const fn as_str(self) -> &'static str {
89 match self {
90 Self::Equal => "eq",
91 Self::NotEqual => "neq",
92 Self::LessThan => "lt",
93 Self::LessThanOrEqual => "lte",
94 Self::GreaterThan => "gt",
95 Self::GreaterThanOrEqual => "gte",
96 Self::In => "in",
97 Self::NotIn => "not-in",
98 Self::Like => "like",
99 Self::IsNull => "is-null",
100 }
101 }
102
103 #[must_use]
105 pub fn from_graphql_key(value: &str) -> Option<Self> {
106 match value {
107 "eq" => Some(Self::Equal),
108 "neq" | "ne" => Some(Self::NotEqual),
109 "lt" => Some(Self::LessThan),
110 "lte" => Some(Self::LessThanOrEqual),
111 "gt" => Some(Self::GreaterThan),
112 "gte" => Some(Self::GreaterThanOrEqual),
113 "in" => Some(Self::In),
114 "notIn" => Some(Self::NotIn),
115 "like" => Some(Self::Like),
116 "isNull" => Some(Self::IsNull),
117 _ => None,
118 }
119 }
120}
121
122#[derive(Clone, Debug, PartialEq)]
124pub enum QueryValue {
125 Null,
127 Boolean(bool),
129 Integer(i64),
131 Float(f64),
133 String(String),
136 List(Vec<Self>),
138 Object(BTreeMap<String, Self>),
140}
141
142impl QueryValue {
143 #[must_use]
145 pub fn to_json(&self) -> Value {
146 match self {
147 Self::Null => Value::Null,
148 Self::Boolean(value) => Value::Bool(*value),
149 Self::Integer(value) => Value::Number(Number::from(*value)),
150 Self::Float(value) => Value::Number(
151 Number::from_f64(*value).expect("query plan floating-point values must be finite"),
152 ),
153 Self::String(value) => Value::String(value.clone()),
154 Self::List(values) => Value::Array(values.iter().map(Self::to_json).collect()),
155 Self::Object(values) => Value::Object(
156 values
157 .iter()
158 .map(|(key, value)| (key.clone(), value.to_json()))
159 .collect(),
160 ),
161 }
162 }
163}
164
165#[derive(Clone, Debug, PartialEq)]
167pub enum QueryFilter {
168 Compare {
170 field: String,
172 operator: QueryComparisonOperator,
174 value: QueryValue,
176 },
177 And(Vec<Self>),
179 Or(Vec<Self>),
181 Not(Box<Self>),
183}
184
185impl QueryFilter {
186 fn to_json(&self) -> Value {
187 match self {
188 Self::Compare {
189 field,
190 operator,
191 value,
192 } => json!({
193 "compare": {
194 "field": field,
195 "operator": operator.as_str(),
196 "value": value.to_json(),
197 }
198 }),
199 Self::And(children) => {
200 json!({"and": children.iter().map(Self::to_json).collect::<Vec<_>>()})
201 }
202 Self::Or(children) => {
203 json!({"or": children.iter().map(Self::to_json).collect::<Vec<_>>()})
204 }
205 Self::Not(child) => json!({"not": child.to_json()}),
206 }
207 }
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
212pub enum QuerySortDirection {
213 Ascending,
215 Descending,
217}
218
219impl QuerySortDirection {
220 #[must_use]
222 pub const fn as_str(self) -> &'static str {
223 match self {
224 Self::Ascending => "asc",
225 Self::Descending => "desc",
226 }
227 }
228}
229
230#[derive(Clone, Debug, PartialEq, Eq)]
232pub struct QueryOrder {
233 pub(crate) field: String,
234 pub(crate) direction: QuerySortDirection,
235}
236
237impl QueryOrder {
238 #[must_use]
240 pub fn new(field: impl Into<String>, direction: QuerySortDirection) -> Self {
241 Self {
242 field: field.into(),
243 direction,
244 }
245 }
246
247 #[must_use]
249 pub fn field(&self) -> &str {
250 &self.field
251 }
252
253 #[must_use]
255 pub const fn direction(&self) -> QuerySortDirection {
256 self.direction
257 }
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq)]
262pub enum QueryAggregateFunction {
263 Count,
265 Sum,
267 Average,
269 Minimum,
271 Maximum,
273 PopulationVariance,
275 PopulationStandardDeviation,
277}
278
279impl QueryAggregateFunction {
280 #[must_use]
282 pub const fn as_str(self) -> &'static str {
283 match self {
284 Self::Count => "count",
285 Self::Sum => "sum",
286 Self::Average => "avg",
287 Self::Minimum => "min",
288 Self::Maximum => "max",
289 Self::PopulationVariance => "variance-population",
290 Self::PopulationStandardDeviation => "stddev-population",
291 }
292 }
293
294 #[must_use]
296 pub fn parse(value: &str) -> Option<Self> {
297 match value {
298 "count" => Some(Self::Count),
299 "sum" => Some(Self::Sum),
300 "avg" => Some(Self::Average),
301 "min" => Some(Self::Minimum),
302 "max" => Some(Self::Maximum),
303 "variance-population" => Some(Self::PopulationVariance),
304 "stddev-population" => Some(Self::PopulationStandardDeviation),
305 _ => None,
306 }
307 }
308}
309
310#[derive(Clone, Debug, PartialEq, Eq)]
312pub struct QueryAggregate {
313 pub(crate) function: QueryAggregateFunction,
314 pub(crate) field: Option<String>,
315 pub(crate) alias: Option<String>,
316}
317
318impl QueryAggregate {
319 #[must_use]
321 pub const fn new(
322 function: QueryAggregateFunction,
323 field: Option<String>,
324 alias: Option<String>,
325 ) -> Self {
326 Self {
327 function,
328 field,
329 alias,
330 }
331 }
332
333 #[must_use]
335 pub const fn function(&self) -> QueryAggregateFunction {
336 self.function
337 }
338
339 #[must_use]
341 pub fn field(&self) -> Option<&str> {
342 self.field.as_deref()
343 }
344
345 #[must_use]
347 pub fn alias(&self) -> Option<&str> {
348 self.alias.as_deref()
349 }
350}
351
352#[derive(Clone, Debug, PartialEq, Eq)]
354pub struct QuerySourceEvidence {
355 role: String,
356 span: SourceSpan,
357}
358
359impl QuerySourceEvidence {
360 #[must_use]
362 pub fn new(role: impl Into<String>, span: SourceSpan) -> Self {
363 Self {
364 role: role.into(),
365 span,
366 }
367 }
368
369 #[must_use]
371 pub fn role(&self) -> &str {
372 &self.role
373 }
374
375 #[must_use]
377 pub const fn span(&self) -> SourceSpan {
378 self.span
379 }
380}
381
382#[derive(Clone, Debug, PartialEq)]
384pub struct QueryPlan {
385 pub(crate) operation: QueryOperation,
386 pub(crate) resource: String,
387 pub(crate) projection: Vec<String>,
388 pub(crate) filter: Option<QueryFilter>,
389 pub(crate) order: Vec<QueryOrder>,
390 pub(crate) limit: Option<u64>,
391 pub(crate) offset: Option<u64>,
392 pub(crate) group_by: Vec<String>,
393 pub(crate) aggregates: Vec<QueryAggregate>,
394 pub(crate) mutation: BTreeMap<String, QueryValue>,
395 pub(crate) source_evidence: Vec<QuerySourceEvidence>,
396}
397
398impl QueryPlan {
399 #[must_use]
401 pub fn new(operation: QueryOperation, resource: impl Into<String>) -> Self {
402 Self {
403 operation,
404 resource: resource.into(),
405 projection: Vec::new(),
406 filter: None,
407 order: Vec::new(),
408 limit: None,
409 offset: None,
410 group_by: Vec::new(),
411 aggregates: Vec::new(),
412 mutation: BTreeMap::new(),
413 source_evidence: Vec::new(),
414 }
415 }
416
417 pub fn add_projection(&mut self, field: impl Into<String>) {
419 self.projection.push(field.into());
420 }
421
422 pub fn set_filter(&mut self, filter: QueryFilter) {
424 self.filter = Some(filter);
425 }
426
427 pub fn add_order(&mut self, order: QueryOrder) {
429 self.order.push(order);
430 }
431
432 pub const fn set_pagination(&mut self, limit: Option<u64>, offset: Option<u64>) {
434 self.limit = limit;
435 self.offset = offset;
436 }
437
438 pub fn add_group_by(&mut self, field: impl Into<String>) {
440 self.group_by.push(field.into());
441 }
442
443 pub fn add_aggregate(&mut self, aggregate: QueryAggregate) {
445 self.aggregates.push(aggregate);
446 }
447
448 pub fn set_mutation_value(&mut self, field: impl Into<String>, value: QueryValue) {
450 self.mutation.insert(field.into(), value);
451 }
452
453 pub fn add_source_evidence(&mut self, evidence: QuerySourceEvidence) {
455 self.source_evidence.push(evidence);
456 }
457
458 #[must_use]
460 pub const fn operation(&self) -> QueryOperation {
461 self.operation
462 }
463
464 #[must_use]
466 pub fn resource(&self) -> &str {
467 &self.resource
468 }
469
470 #[must_use]
472 pub fn projection(&self) -> &[String] {
473 &self.projection
474 }
475
476 #[must_use]
478 pub const fn filter(&self) -> Option<&QueryFilter> {
479 self.filter.as_ref()
480 }
481
482 #[must_use]
484 pub fn order(&self) -> &[QueryOrder] {
485 &self.order
486 }
487
488 #[must_use]
490 pub const fn limit(&self) -> Option<u64> {
491 self.limit
492 }
493
494 #[must_use]
496 pub const fn offset(&self) -> Option<u64> {
497 self.offset
498 }
499
500 #[must_use]
502 pub fn group_by(&self) -> &[String] {
503 &self.group_by
504 }
505
506 #[must_use]
508 pub fn aggregates(&self) -> &[QueryAggregate] {
509 &self.aggregates
510 }
511
512 #[must_use]
514 pub const fn mutation(&self) -> &BTreeMap<String, QueryValue> {
515 &self.mutation
516 }
517
518 #[must_use]
520 pub fn source_evidence(&self) -> &[QuerySourceEvidence] {
521 &self.source_evidence
522 }
523
524 #[must_use]
526 pub const fn authorization(&self) -> QueryAuthorization {
527 QueryAuthorization::Required
528 }
529
530 #[must_use]
533 pub fn canonical_value(&self) -> Value {
534 let order = self
535 .order
536 .iter()
537 .map(|entry| {
538 json!({
539 "field": entry.field,
540 "direction": entry.direction.as_str(),
541 })
542 })
543 .collect::<Vec<_>>();
544 let aggregates = self
545 .aggregates
546 .iter()
547 .map(|aggregate| {
548 json!({
549 "function": aggregate.function.as_str(),
550 "field": aggregate.field,
551 "alias": aggregate.alias,
552 })
553 })
554 .collect::<Vec<_>>();
555 let mutation = self
556 .mutation
557 .iter()
558 .map(|(field, value)| (field.clone(), value.to_json()))
559 .collect::<Map<_, _>>();
560
561 json!({
562 "version": QUERY_PLAN_VERSION,
563 "operation": self.operation.as_str(),
564 "resource": self.resource,
565 "projection": self.projection,
566 "filter": self.filter.as_ref().map(QueryFilter::to_json),
567 "order": order,
568 "limit": self.limit,
569 "offset": self.offset,
570 "groupBy": self.group_by,
571 "aggregates": aggregates,
572 "mutation": mutation,
573 })
574 }
575
576 #[must_use]
579 pub fn canonical_json(&self) -> String {
580 serde_json::to_string(&self.canonical_value()).expect("query plan values are serializable")
581 }
582}
583
584#[derive(Clone, Debug, PartialEq)]
586pub struct LoweredQueryPlan {
587 plan: QueryPlan,
588 network: LinkNetwork,
589 root_link: LinkId,
590}
591
592impl LoweredQueryPlan {
593 pub(crate) const fn new(plan: QueryPlan, network: LinkNetwork, root_link: LinkId) -> Self {
594 Self {
595 plan,
596 network,
597 root_link,
598 }
599 }
600
601 #[must_use]
603 pub const fn plan(&self) -> &QueryPlan {
604 &self.plan
605 }
606
607 #[must_use]
609 pub const fn network(&self) -> &LinkNetwork {
610 &self.network
611 }
612
613 #[must_use]
615 pub const fn root_link(&self) -> LinkId {
616 self.root_link
617 }
618
619 #[must_use]
621 pub fn into_parts(self) -> (QueryPlan, LinkNetwork, LinkId) {
622 (self.plan, self.network, self.root_link)
623 }
624}
625
626pub(crate) fn attach_plan_links(
627 network: &mut LinkNetwork,
628 plan: &QueryPlan,
629 language: &str,
630) -> LinkId {
631 let cst_links = plan
632 .source_evidence
633 .iter()
634 .map(|evidence| closest_cst(network, evidence.span()))
635 .collect::<Vec<_>>();
636 let plan_concept = network.insert_point("executable-query-plan");
637 let mut references = vec![plan_concept];
638 for (evidence, cst) in plan.source_evidence.iter().zip(cst_links) {
639 let concept = network.insert_point(evidence.role());
640 let mut child_references = vec![concept];
641 if let Some(cst) = cst {
642 child_references.push(cst);
643 }
644 let child = network.insert_dynamic_link(
645 &child_references,
646 LinkMetadata::new()
647 .with_link_type(LinkType::Semantic)
648 .with_named(true)
649 .with_term(evidence.role())
650 .with_language(language)
651 .with_span(evidence.span()),
652 );
653 references.push(child);
654 }
655 if let Some(cst) = plan
656 .source_evidence
657 .first()
658 .and_then(|evidence| closest_cst(network, evidence.span()))
659 {
660 references.push(cst);
661 }
662 let root_span = plan
663 .source_evidence
664 .first()
665 .map(QuerySourceEvidence::span)
666 .expect("lowered plans always retain root evidence");
667 network.insert_dynamic_link(
668 &references,
669 LinkMetadata::new()
670 .with_link_type(LinkType::Semantic)
671 .with_named(true)
672 .with_term("executable-query-plan")
673 .with_language(language)
674 .with_span(root_span),
675 )
676}
677
678fn closest_cst(network: &LinkNetwork, span: SourceSpan) -> Option<LinkId> {
679 let target = span.byte_range();
680 network
681 .links()
682 .filter(|link| link.metadata().link_type() == Some(LinkType::Syntax))
683 .filter_map(|link| Some((link, link.metadata().span()?.byte_range())))
684 .filter(|(_, candidate)| {
685 candidate.start() <= target.start() && candidate.end() >= target.end()
686 })
687 .min_by_key(|(_, candidate)| candidate.end() - candidate.start())
688 .map(|(link, _)| Link::id(link))
689}