openehr_query/ast.rs
1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-FileCopyrightText: openEHR Foundation
3// SPDX-License-Identifier: Apache-2.0
4
5//! AQL abstract syntax tree, transcribed from `AqlParser.g4` (vendored at
6//! `vendor/grammar/`).
7//!
8//! Each grammar rule maps to a type here; [`crate::parser`] builds these from
9//! the [`crate::lexer`] token stream. This is the SYNTACTIC tree only —
10//! resolving paths and typing quoted temporal literals are semantic concerns.
11//!
12//! Every [`IdentifiedPath`] and every WHERE condition carries a [`Span`]: where
13//! it was written in the source, so a diagnostic can name a position instead of
14//! echoing the text there. Spans never take part in equality.
15
16use std::ops::Range;
17
18use crate::lexer::CompOp;
19
20/// Where a node was written: a half-open byte range into the AQL source.
21///
22/// A span is position metadata, not syntax, so equality ignores it: two nodes
23/// are equal when their syntax is, wherever each was written. That keeps the
24/// printer's round-trip invariant (`parse(to_aql(ast)) == ast`) a statement
25/// about syntax alone. A node built in code, or parsed from a bare token slice
26/// ([`crate::parser::parse`]), has no source position and carries the
27/// [`Span::default`] unknown span.
28///
29/// Offsets are held as `u32`, which keeps a span small enough to ride on every
30/// path; a position past 4 GiB of source is not representable and reads as
31/// unknown.
32#[derive(Debug, Clone, Copy, Default)]
33pub struct Span {
34 bytes: Option<(u32, u32)>,
35}
36
37impl Span {
38 /// A span over `bytes` of the source.
39 #[must_use]
40 pub fn new(bytes: Range<usize>) -> Self {
41 let start = u32::try_from(bytes.start).ok();
42 let end = u32::try_from(bytes.end).ok();
43 Self {
44 bytes: start.zip(end),
45 }
46 }
47
48 /// The half-open byte range of the source this node was written at, or
49 /// `None` when the node has no source position.
50 #[must_use]
51 pub fn bytes(&self) -> Option<Range<usize>> {
52 let (start, end) = self.bytes?;
53 let start = usize::try_from(start).ok()?;
54 let end = usize::try_from(end).ok()?;
55 Some(start..end)
56 }
57}
58
59impl PartialEq for Span {
60 fn eq(&self, _other: &Self) -> bool {
61 true
62 }
63}
64
65impl Eq for Span {}
66
67/// `selectQuery : selectClause fromClause whereClause? orderByClause? limitClause?`
68#[derive(Debug, Clone, PartialEq)]
69pub struct SelectQuery {
70 /// `SELECT [DISTINCT] [TOP n] col, …`
71 pub select: SelectClause,
72 /// `FROM <containsExpr>`
73 pub from: ContainsExpr,
74 /// `WHERE <whereExpr>`
75 pub where_: Option<WhereExpr>,
76 /// `ORDER BY …`
77 pub order_by: Vec<OrderByExpr>,
78 /// `LIMIT n [OFFSET m]`
79 pub limit: Option<Limit>,
80}
81
82/// `selectClause : SELECT DISTINCT? top? selectExpr (',' selectExpr)*`
83#[derive(Debug, Clone, PartialEq)]
84pub struct SelectClause {
85 /// `DISTINCT` present.
86 pub distinct: bool,
87 /// The deprecated `TOP n [FORWARD|BACKWARD]`.
88 pub top: Option<Top>,
89 /// One or more selected columns.
90 pub columns: Vec<SelectExpr>,
91}
92
93/// The deprecated `top : TOP INTEGER (FORWARD|BACKWARD)?`.
94#[derive(Debug, Clone, PartialEq)]
95pub struct Top {
96 /// The row count.
97 pub count: i64,
98 /// Optional direction.
99 pub direction: Option<TopDirection>,
100}
101
102/// `TOP` direction.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum TopDirection {
105 /// `FORWARD`
106 Forward,
107 /// `BACKWARD`
108 Backward,
109}
110
111/// `selectExpr : columnExpr (AS aliasName)?`
112#[derive(Debug, Clone, PartialEq)]
113pub struct SelectExpr {
114 /// The selected expression.
115 pub column: ColumnExpr,
116 /// Optional `AS alias`.
117 pub alias: Option<String>,
118}
119
120/// `columnExpr : identifiedPath | primitive | aggregateFunctionCall | functionCall`
121#[derive(Debug, Clone, PartialEq)]
122pub enum ColumnExpr {
123 /// A data path.
124 Path(IdentifiedPath),
125 /// A literal.
126 Primitive(Primitive),
127 /// `COUNT`/`MIN`/`MAX`/`SUM`/`AVG`.
128 Aggregate(AggregateCall),
129 /// A named function call.
130 Function(FunctionCall),
131}
132
133/// `containsExpr` — a boolean tree whose leaves are class expressions each
134/// optionally constrained by a nested `CONTAINS`.
135#[derive(Debug, Clone, PartialEq)]
136pub enum ContainsExpr {
137 /// `classExprOperand (NOT? CONTAINS containsExpr)?`
138 Contained {
139 /// The class/version operand.
140 operand: ClassExprOperand,
141 /// Optional `[NOT] CONTAINS <sub>`.
142 contains: Option<Box<ContainsConstraint>>,
143 },
144 /// `containsExpr AND containsExpr`
145 And(Box<ContainsExpr>, Box<ContainsExpr>),
146 /// `containsExpr OR containsExpr`
147 Or(Box<ContainsExpr>, Box<ContainsExpr>),
148}
149
150/// The `[NOT] CONTAINS <containsExpr>` tail of a [`ContainsExpr::Contained`].
151#[derive(Debug, Clone, PartialEq)]
152pub struct ContainsConstraint {
153 /// `NOT CONTAINS`.
154 pub negated: bool,
155 /// The contained sub-expression.
156 pub expr: ContainsExpr,
157}
158
159/// `classExprOperand : #classExpression | #versionClassExpr`
160#[derive(Debug, Clone, PartialEq)]
161pub enum ClassExprOperand {
162 /// `IDENTIFIER variable? pathPredicate?` (e.g. `COMPOSITION c[openEHR-…]`).
163 Class {
164 /// The RM class name.
165 rm_type: String,
166 /// Optional bound variable.
167 variable: Option<String>,
168 /// Optional `[predicate]`.
169 predicate: Option<PathPredicate>,
170 },
171 /// `VERSION variable? [versionPredicate]?`
172 Version {
173 /// Optional bound variable.
174 variable: Option<String>,
175 /// Optional version predicate.
176 predicate: Option<VersionPredicate>,
177 },
178}
179
180/// `whereExpr` — a boolean tree of [`IdentifiedExpr`] leaves.
181#[derive(Debug, Clone, PartialEq)]
182pub enum WhereExpr {
183 /// A leaf condition, and where it was written.
184 Identified(IdentifiedExpr, Span),
185 /// `NOT whereExpr`
186 Not(Box<WhereExpr>),
187 /// `whereExpr AND whereExpr`
188 And(Box<WhereExpr>, Box<WhereExpr>),
189 /// `whereExpr OR whereExpr`
190 Or(Box<WhereExpr>, Box<WhereExpr>),
191}
192
193impl WhereExpr {
194 /// A leaf condition with no source position, for a tree built in code.
195 #[must_use]
196 pub fn identified(expr: IdentifiedExpr) -> Self {
197 Self::Identified(expr, Span::default())
198 }
199}
200
201/// `identifiedExpr` — a single WHERE condition.
202#[derive(Debug, Clone, PartialEq)]
203pub enum IdentifiedExpr {
204 /// `EXISTS identifiedPath`
205 Exists(IdentifiedPath),
206 /// `identifiedPath|functionCall COMPARISON_OPERATOR terminal`
207 Compare {
208 /// Left operand.
209 lhs: CompareOperand,
210 /// The operator.
211 op: CompOp,
212 /// Right operand.
213 rhs: Terminal,
214 },
215 /// `identifiedPath LIKE likeOperand`
216 Like {
217 /// The path.
218 path: IdentifiedPath,
219 /// String or parameter.
220 operand: LikeOperand,
221 },
222 /// `identifiedPath MATCHES matchesOperand`
223 Matches {
224 /// The path.
225 path: IdentifiedPath,
226 /// The match set.
227 operand: MatchesOperand,
228 },
229 /// A condition already resolved to a constant during semantic analysis —
230 /// the product of evaluating a `TERMINOLOGY()` Boolean value expression
231 /// (QUERY master03 §TERMINOLOGY: "as a Boolean value expression"). Never
232 /// produced by the parser.
233 Resolved(bool),
234}
235
236/// Left side of a comparison: a path or a function call.
237#[derive(Debug, Clone, PartialEq)]
238pub enum CompareOperand {
239 /// `identifiedPath`
240 Path(IdentifiedPath),
241 /// `functionCall`
242 Function(FunctionCall),
243}
244
245/// `terminal : primitive | PARAMETER | identifiedPath | functionCall`
246#[derive(Debug, Clone, PartialEq)]
247pub enum Terminal {
248 /// A literal.
249 Primitive(Primitive),
250 /// `$param`.
251 Parameter(String),
252 /// A path.
253 Path(IdentifiedPath),
254 /// A function call.
255 Function(FunctionCall),
256}
257
258/// `orderByExpr : identifiedPath (DESCENDING|DESC|ASCENDING|ASC)?`
259#[derive(Debug, Clone, PartialEq)]
260pub struct OrderByExpr {
261 /// The path to order by.
262 pub path: IdentifiedPath,
263 /// Sort direction (defaults to ascending when absent).
264 pub order: Option<SortOrder>,
265}
266
267/// Sort direction.
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum SortOrder {
270 /// `ASC` / `ASCENDING`
271 Ascending,
272 /// `DESC` / `DESCENDING`
273 Descending,
274}
275
276/// `limitClause : LIMIT INTEGER (OFFSET INTEGER)?`
277#[derive(Debug, Clone, PartialEq, Eq)]
278pub struct Limit {
279 /// Row limit.
280 pub limit: i64,
281 /// Optional offset.
282 pub offset: Option<i64>,
283}
284
285/// `identifiedPath : IDENTIFIER pathPredicate? ('/' objectPath)?`
286#[derive(Debug, Clone, PartialEq)]
287pub struct IdentifiedPath {
288 /// The root variable/identifier.
289 pub root: String,
290 /// Optional predicate on the root.
291 pub predicate: Option<PathPredicate>,
292 /// Optional trailing `/a/b/c` object path.
293 pub path: Option<ObjectPath>,
294 /// Where the path was written, root to last part.
295 pub span: Span,
296}
297
298impl IdentifiedPath {
299 /// A path with no source position, for a tree built in code.
300 #[must_use]
301 pub fn new(root: String, predicate: Option<PathPredicate>, path: Option<ObjectPath>) -> Self {
302 Self {
303 root,
304 predicate,
305 path,
306 span: Span::default(),
307 }
308 }
309}
310
311/// `objectPath : pathPart ('/' pathPart)*`
312#[derive(Debug, Clone, PartialEq)]
313pub struct ObjectPath {
314 /// The `/`-separated parts.
315 pub parts: Vec<PathPart>,
316}
317
318/// `pathPart : IDENTIFIER pathPredicate?`
319#[derive(Debug, Clone, PartialEq)]
320pub struct PathPart {
321 /// The attribute name.
322 pub name: String,
323 /// Optional `[predicate]`.
324 pub predicate: Option<PathPredicate>,
325}
326
327/// `pathPredicate : '[' (standardPredicate | archetypePredicate | nodePredicate) ']'`
328#[derive(Debug, Clone, PartialEq)]
329pub enum PathPredicate {
330 /// `objectPath COMPARISON_OPERATOR operand`
331 Standard(Box<StandardPredicate>),
332 /// `ARCHETYPE_HRID | PARAMETER`
333 Archetype(ArchetypePredicate),
334 /// A node predicate (id/at code, archetype id, boolean tree, …).
335 Node(Box<NodePredicate>),
336}
337
338/// `standardPredicate : objectPath COMPARISON_OPERATOR pathPredicateOperand`
339#[derive(Debug, Clone, PartialEq)]
340pub struct StandardPredicate {
341 /// The constrained path.
342 pub path: ObjectPath,
343 /// The operator.
344 pub op: CompOp,
345 /// The operand.
346 pub operand: PathPredicateOperand,
347}
348
349/// `archetypePredicate : ARCHETYPE_HRID | PARAMETER`
350#[derive(Debug, Clone, PartialEq)]
351pub enum ArchetypePredicate {
352 /// An archetype HRID.
353 Hrid(String),
354 /// `$param`.
355 Parameter(String),
356}
357
358/// `nodePredicate` — the (partial) common forms: a node code with optional
359/// name/term, an archetype id with optional name/term, a parameter, a standard
360/// comparison, or a boolean combination.
361#[derive(Debug, Clone, PartialEq)]
362pub enum NodePredicate {
363 /// `(ID_CODE|AT_CODE) (',' (STRING|PARAMETER|TERM_CODE|AT_CODE|ID_CODE))?`
364 Code {
365 /// The `id`/`at` code.
366 code: String,
367 /// Optional name/term operand.
368 name: Option<NodeNameConstraint>,
369 },
370 /// `ARCHETYPE_HRID (',' …)?`
371 Archetype {
372 /// The archetype HRID.
373 hrid: String,
374 /// Optional name/term operand.
375 name: Option<NodeNameConstraint>,
376 },
377 /// `$param`
378 Parameter(String),
379 /// `objectPath COMPARISON_OPERATOR operand`
380 Standard(Box<StandardPredicate>),
381 /// `objectPath MATCHES CONTAINED_REGEX`
382 MatchesRegex {
383 /// The constrained path.
384 path: ObjectPath,
385 /// The raw `{/regex/}` token text.
386 regex: String,
387 },
388 /// `nodePredicate AND nodePredicate`
389 And(Box<NodePredicate>, Box<NodePredicate>),
390 /// `nodePredicate OR nodePredicate`
391 Or(Box<NodePredicate>, Box<NodePredicate>),
392}
393
394/// The optional name/term after a node code in a [`NodePredicate`].
395#[derive(Debug, Clone, PartialEq)]
396pub enum NodeNameConstraint {
397 /// A quoted name.
398 String(String),
399 /// `$param`.
400 Parameter(String),
401 /// A term code.
402 TermCode(String),
403 /// An `at`/`id` code.
404 Code(String),
405}
406
407/// `versionPredicate : LATEST_VERSION | ALL_VERSIONS | standardPredicate`
408#[derive(Debug, Clone, PartialEq)]
409pub enum VersionPredicate {
410 /// `LATEST_VERSION`
411 Latest,
412 /// `ALL_VERSIONS`
413 All,
414 /// A standard comparison predicate.
415 Standard(Box<StandardPredicate>),
416}
417
418/// `pathPredicateOperand : primitive | objectPath | PARAMETER | ID_CODE | AT_CODE`
419#[derive(Debug, Clone, PartialEq)]
420pub enum PathPredicateOperand {
421 /// A literal.
422 Primitive(Primitive),
423 /// A path.
424 Path(ObjectPath),
425 /// `$param`.
426 Parameter(String),
427 /// An `id`/`at` code.
428 Code(String),
429}
430
431/// `likeOperand : STRING | PARAMETER`
432#[derive(Debug, Clone, PartialEq)]
433pub enum LikeOperand {
434 /// A quoted pattern.
435 String(String),
436 /// `$param`.
437 Parameter(String),
438}
439
440/// `matchesOperand : '{' valueListItem (',' valueListItem)* '}' | terminologyFunction | '{' URI '}'`
441#[derive(Debug, Clone, PartialEq)]
442pub enum MatchesOperand {
443 /// A `{ … }` value list.
444 ValueList(Vec<ValueListItem>),
445 /// `terminology(...)`.
446 Terminology(TerminologyFunction),
447 /// `{ uri }`.
448 Uri(String),
449}
450
451/// `valueListItem : primitive | PARAMETER | terminologyFunction`
452#[derive(Debug, Clone, PartialEq)]
453pub enum ValueListItem {
454 /// A literal.
455 Primitive(Primitive),
456 /// `$param`.
457 Parameter(String),
458 /// `terminology(...)`.
459 Terminology(TerminologyFunction),
460}
461
462/// `functionCall` — a named function with terminal arguments (also covers
463/// `terminologyFunction`, kept as [`FunctionCall::Terminology`]).
464///
465/// A name is classified against the built-in functions QUERY master03
466/// §Functions defines (the grammar's `STRING_FUNCTION_ID`,
467/// `NUMERIC_FUNCTION_ID` and `DATE_TIME_FUNCTION_ID` groups); a name outside
468/// them is [`FunctionCall::Other`]. Both keep the name as spelled, so the
469/// printer round-trips it.
470#[derive(Debug, Clone, PartialEq)]
471pub enum FunctionCall {
472 /// `name ( terminal, … )` naming an AQL built-in function.
473 Builtin {
474 /// The function the name denotes.
475 function: BuiltinFunction,
476 /// The name as written (AQL names are case-insensitive).
477 name: String,
478 /// The arguments.
479 args: Vec<Terminal>,
480 },
481 /// `IDENTIFIER ( terminal, … )` naming no AQL built-in function: the
482 /// spec's "various other functions may exist however in various AQL
483 /// implementations" (QUERY master03 §Functions).
484 Other {
485 /// The name as written.
486 name: String,
487 /// The arguments.
488 args: Vec<Terminal>,
489 },
490 /// `terminology(str, str, str)`.
491 Terminology(TerminologyFunction),
492}
493
494impl FunctionCall {
495 /// The call `name(args)`, classified: a built-in function when the name
496 /// denotes one, [`FunctionCall::Other`] otherwise.
497 #[must_use]
498 pub fn named(name: String, args: Vec<Terminal>) -> Self {
499 match BuiltinFunction::from_name(&name) {
500 Some(function) => Self::Builtin {
501 function,
502 name,
503 args,
504 },
505 None => Self::Other { name, args },
506 }
507 }
508}
509
510/// An AQL built-in single-row function (QUERY master03 §Functions), by group.
511///
512/// The aggregates and `TERMINOLOGY` are not here: their argument grammar
513/// differs, so the AST carries them as [`AggregateCall`] and
514/// [`TerminologyFunction`].
515#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
516pub enum BuiltinFunction {
517 /// A string function (`STRING_FUNCTION_ID`).
518 String(StringFunction),
519 /// A numeric function (`NUMERIC_FUNCTION_ID`).
520 Numeric(NumericFunction),
521 /// A date and time function (`DATE_TIME_FUNCTION_ID`).
522 DateTime(DateTimeFunction),
523}
524
525impl BuiltinFunction {
526 /// The built-in function `name` denotes, compared case-insensitively, or
527 /// `None` when AQL defines no function of that name.
528 #[must_use]
529 pub fn from_name(name: &str) -> Option<Self> {
530 StringFunction::from_name(name)
531 .map(Self::String)
532 .or_else(|| NumericFunction::from_name(name).map(Self::Numeric))
533 .or_else(|| DateTimeFunction::from_name(name).map(Self::DateTime))
534 }
535
536 /// The function's name as the specification spells it.
537 #[must_use]
538 pub const fn as_str(self) -> &'static str {
539 match self {
540 Self::String(f) => f.as_str(),
541 Self::Numeric(f) => f.as_str(),
542 Self::DateTime(f) => f.as_str(),
543 }
544 }
545}
546
547/// Declares one function-id group: the enum, its spelled names, and the
548/// case-insensitive lookup.
549macro_rules! function_group {
550 ($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident => $spelling:literal,)+ }) => {
551 $(#[$meta])*
552 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
553 pub enum $name {
554 $($(#[$vmeta])* $variant,)+
555 }
556
557 impl $name {
558 /// Every function of the group, in specification order.
559 pub const ALL: &[Self] = &[$(Self::$variant,)+];
560
561 /// The function `name` denotes, compared case-insensitively.
562 #[must_use]
563 pub fn from_name(name: &str) -> Option<Self> {
564 Self::ALL
565 .iter()
566 .copied()
567 .find(|f| f.as_str().eq_ignore_ascii_case(name))
568 }
569
570 /// The function's name as the specification spells it.
571 #[must_use]
572 pub const fn as_str(self) -> &'static str {
573 match self {
574 $(Self::$variant => $spelling,)+
575 }
576 }
577 }
578 };
579}
580
581function_group! {
582 /// A string function (QUERY master03 §Functions/String functions).
583 StringFunction {
584 /// `LENGTH(expression)`.
585 Length => "LENGTH",
586 /// `CONTAINS(expression, substring)`, the string function, not the
587 /// containment operator.
588 Contains => "CONTAINS",
589 /// `POSITION(substring, expression)`.
590 Position => "POSITION",
591 /// `SUBSTRING(expression, position[, length])`.
592 Substring => "SUBSTRING",
593 /// `CONCAT(expr1, expr2, …)`.
594 Concat => "CONCAT",
595 /// `CONCAT_WS(separator, expr1, expr2, …)`.
596 ConcatWs => "CONCAT_WS",
597 }
598}
599
600function_group! {
601 /// A numeric function (QUERY master03 §Functions/Numeric functions).
602 NumericFunction {
603 /// `ABS(expression)`.
604 Abs => "ABS",
605 /// `MOD(x, y)`.
606 Mod => "MOD",
607 /// `CEIL(expression)`.
608 Ceil => "CEIL",
609 /// `FLOOR(expression)`.
610 Floor => "FLOOR",
611 /// `ROUND(expression[, decimal])`.
612 Round => "ROUND",
613 }
614}
615
616function_group! {
617 /// A date and time function (QUERY master03 §Functions/Date and time
618 /// functions).
619 DateTimeFunction {
620 /// `CURRENT_DATE()`.
621 CurrentDate => "CURRENT_DATE",
622 /// `CURRENT_TIME()`.
623 CurrentTime => "CURRENT_TIME",
624 /// `CURRENT_DATE_TIME()`.
625 CurrentDateTime => "CURRENT_DATE_TIME",
626 /// `NOW()`, the specification's alias for `CURRENT_DATE_TIME()`, kept
627 /// apart so the query prints as written.
628 Now => "NOW",
629 /// `CURRENT_TIMEZONE()`.
630 CurrentTimezone => "CURRENT_TIMEZONE",
631 }
632}
633
634/// `terminologyFunction : TERMINOLOGY '(' STRING ',' STRING ',' STRING ')'`
635#[derive(Debug, Clone, PartialEq)]
636pub struct TerminologyFunction {
637 /// First argument (operation).
638 pub operation: String,
639 /// Second argument.
640 pub arg2: String,
641 /// Third argument.
642 pub arg3: String,
643}
644
645/// `aggregateFunctionCall`
646#[derive(Debug, Clone, PartialEq)]
647pub enum AggregateCall {
648 /// `COUNT ( DISTINCT? identifiedPath | '*' )`
649 Count {
650 /// `DISTINCT` present.
651 distinct: bool,
652 /// The path, or `None` for `COUNT(*)`.
653 path: Option<IdentifiedPath>,
654 },
655 /// `MIN|MAX|SUM|AVG ( identifiedPath )`
656 Stat {
657 /// Which aggregate.
658 func: StatFunc,
659 /// The path.
660 path: IdentifiedPath,
661 },
662}
663
664/// The non-count aggregate functions.
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
666pub enum StatFunc {
667 /// `MIN`
668 Min,
669 /// `MAX`
670 Max,
671 /// `SUM`
672 Sum,
673 /// `AVG`
674 Avg,
675}
676
677/// `primitive` — an AQL literal.
678#[derive(Debug, Clone, PartialEq)]
679pub enum Primitive {
680 /// A quoted string (unescaping/temporal-typing deferred; raw slice sans
681 /// surrounding quotes).
682 String(String),
683 /// An integer literal.
684 Integer(i64),
685 /// A real literal.
686 Real(f64),
687 /// A boolean literal.
688 Boolean(bool),
689 /// `NULL`.
690 Null,
691}
692
693// ── path text rendering (RESULT_SET column `path`) ─────────────────────────────
694//
695// ITS-REST 1.1.0 RESULT_SET columns carry the SELECT expression's path
696// (`{"name": "#0", "path": "/ehr_id/value"}`) exactly as written in the query,
697// minus the root variable, rather than a normalized reconstruction.
698
699use std::fmt;
700
701impl IdentifiedPath {
702 /// The `RESULT_SET` column-path text of this select expression: the trailing
703 /// object path (with predicates) as written, `/`-prefixed; a bare variable
704 /// (whole-object select) renders as `"/"`.
705 #[must_use]
706 pub fn column_path_text(&self) -> String {
707 match &self.path {
708 None => "/".to_owned(),
709 Some(p) => format!("/{p}"),
710 }
711 }
712
713 /// The column-path text of this select expression with its root variable
714 /// kept, as the query wrote it: `c/uid/value`, or `c` for a bare variable.
715 ///
716 /// ITS-REST 1.1.0 `ResultSetColumn.path` is the "Path from the given AQL of
717 /// the specified column"; with the variable kept, two variables of one class
718 /// (`c1/uid/value`, `c2/uid/value`) render apart.
719 #[must_use]
720 pub fn column_path_with_variable(&self) -> String {
721 match &self.path {
722 None => self.root.clone(),
723 Some(p) => format!("{}/{p}", self.root),
724 }
725 }
726}
727
728impl fmt::Display for ObjectPath {
729 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730 for (i, part) in self.parts.iter().enumerate() {
731 if i > 0 {
732 f.write_str("/")?;
733 }
734 write!(f, "{part}")?;
735 }
736 Ok(())
737 }
738}
739
740impl fmt::Display for PathPart {
741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742 f.write_str(&self.name)?;
743 if let Some(p) = &self.predicate {
744 write!(f, "[{p}]")?;
745 }
746 Ok(())
747 }
748}
749
750impl fmt::Display for PathPredicate {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 match self {
753 PathPredicate::Standard(s) => write!(f, "{s}"),
754 PathPredicate::Archetype(ArchetypePredicate::Hrid(h)) => f.write_str(h),
755 PathPredicate::Archetype(ArchetypePredicate::Parameter(p)) => f.write_str(p),
756 PathPredicate::Node(n) => write!(f, "{n}"),
757 }
758 }
759}
760
761impl fmt::Display for StandardPredicate {
762 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
763 write!(f, "{}{}{}", self.path, comp_op_text(self.op), self.operand)
764 }
765}
766
767/// The AQL surface symbol of a comparison operator.
768#[must_use]
769pub fn comp_op_text(op: CompOp) -> &'static str {
770 match op {
771 CompOp::Eq => "=",
772 CompOp::Ne => "!=",
773 CompOp::Gt => ">",
774 CompOp::Ge => ">=",
775 CompOp::Lt => "<",
776 CompOp::Le => "<=",
777 }
778}
779
780impl fmt::Display for NodePredicate {
781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
782 match self {
783 NodePredicate::Code { code, name } | NodePredicate::Archetype { hrid: code, name } => {
784 f.write_str(code)?;
785 if let Some(n) = name {
786 write!(f, ",{n}")?;
787 }
788 Ok(())
789 }
790 NodePredicate::Parameter(p) => f.write_str(p),
791 NodePredicate::Standard(s) => write!(f, "{s}"),
792 NodePredicate::MatchesRegex { path, regex } => {
793 write!(f, "{path} matches {regex}")
794 }
795 NodePredicate::And(a, b) => write!(f, "{a} and {b}"),
796 NodePredicate::Or(a, b) => write!(f, "{a} or {b}"),
797 }
798 }
799}
800
801impl fmt::Display for NodeNameConstraint {
802 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803 match self {
804 NodeNameConstraint::String(s) => {
805 write!(f, "'{}'", crate::printer::escape_string(s))
806 }
807 NodeNameConstraint::Parameter(p) | NodeNameConstraint::Code(p) => f.write_str(p),
808 NodeNameConstraint::TermCode(t) => f.write_str(t),
809 }
810 }
811}
812
813impl fmt::Display for PathPredicateOperand {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 match self {
816 PathPredicateOperand::Primitive(p) => write!(f, "{p}"),
817 PathPredicateOperand::Path(p) => write!(f, "{p}"),
818 PathPredicateOperand::Parameter(p) | PathPredicateOperand::Code(p) => f.write_str(p),
819 }
820 }
821}
822
823impl fmt::Display for Primitive {
824 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825 match self {
826 Primitive::String(s) => write!(f, "'{}'", crate::printer::escape_string(s)),
827 Primitive::Integer(i) => write!(f, "{i}"),
828 Primitive::Real(r) => write!(f, "{r}"),
829 Primitive::Boolean(b) => write!(f, "{b}"),
830 Primitive::Null => f.write_str("NULL"),
831 }
832 }
833}