1use common::fmt::{EscapeKwFreeIdent, Fmt};
2use surrealdb_strand::Strand;
3use surrealdb_types::{SqlFormat, ToSql, write_sql};
4
5use crate::{CoverStmts, Expr, Idiom, Lookup};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum Part {
9 All,
10 Flatten,
11 Last,
12 First,
13 Field(Strand),
14 Where(Expr),
15 Graph(Box<Lookup>),
16 Value(Expr),
17 Start(Expr),
18 Method(Strand, Vec<Expr>),
19 Destructure(Vec<DestructurePart>),
20 Optional,
21 Recurse(Recurse, Option<Idiom>, Option<RecurseInstruction>),
22 Doc,
23 RepeatRecurse,
24}
25
26impl ToSql for Part {
27 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
28 match self {
29 Part::All => f.push_str(".*"),
30 Part::Last => f.push_str("[$]"),
31 Part::First => f.push_str("[0]"),
32 Part::Start(v) => v.fmt_sql(f, fmt),
33 Part::Field(v) => write_sql!(f, fmt, ".{}", EscapeKwFreeIdent(v.as_str())),
34 Part::Flatten => f.push('…'),
35 Part::Where(v) => write_sql!(f, fmt, "[WHERE {v}]"),
36 Part::Graph(v) => v.fmt_sql(f, fmt),
37 Part::Value(v) => write_sql!(f, fmt, "[{v}]"),
38 Part::Method(v, a) => {
39 write_sql!(
40 f,
41 fmt,
42 ".{}({})",
43 EscapeKwFreeIdent(v.as_str()),
44 Fmt::comma_separated(a.iter().map(CoverStmts))
45 )
46 }
47 Part::Destructure(v) => {
48 f.push_str(".{");
49 if !fmt.is_pretty() {
50 f.push(' ');
51 }
52 if !v.is_empty() {
53 let fmt = fmt.increment();
54 write_sql!(f, fmt, "{}", Fmt::pretty_comma_separated(v));
55 }
56 if fmt.is_pretty() {
57 f.push('}');
58 } else {
59 f.push_str(" }");
60 }
61 }
62 Part::Optional => f.push_str(".?"),
63 Part::Recurse(v, nest, instruction) => {
64 write_sql!(f, fmt, ".{{{v}");
65 if let Some(instruction) = instruction {
66 write_sql!(f, fmt, "+{instruction}");
67 }
68 f.push('}');
69
70 if let Some(nest) = nest {
71 f.push('(');
72 for p in nest.0.iter() {
73 p.fmt_sql(f, fmt);
74 }
75 f.push(')');
76 }
77 }
78 Part::Doc => f.push('@'),
79 Part::RepeatRecurse => f.push_str(".@"),
80 }
81 }
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
87#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
88pub enum DestructurePart {
89 All(Strand),
90 Field(Strand),
91 Aliased(Strand, Idiom),
92 Destructure(Strand, Vec<DestructurePart>),
93}
94
95impl ToSql for DestructurePart {
96 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
97 match self {
98 DestructurePart::All(fd) => write_sql!(f, fmt, "{}.*", EscapeKwFreeIdent(fd.as_str())),
99 DestructurePart::Field(fd) => write_sql!(f, fmt, "{}", EscapeKwFreeIdent(fd.as_str())),
100 DestructurePart::Aliased(fd, v) => {
101 write_sql!(f, fmt, "{}: {v}", EscapeKwFreeIdent(fd.as_str()))
102 }
103 DestructurePart::Destructure(fd, d) => {
104 write_sql!(
105 f,
106 fmt,
107 "{}{}",
108 EscapeKwFreeIdent(fd.as_str()),
109 Part::Destructure(d.clone())
110 )
111 }
112 }
113 }
114}
115
116#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
119#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
120pub enum Recurse {
121 Fixed(u32),
122 Range(Option<u32>, Option<u32>),
123}
124
125impl ToSql for Recurse {
126 fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
127 match self {
128 Recurse::Fixed(v) => f.push_str(&v.to_string()),
129 Recurse::Range(beg, end) => match (beg, end) {
130 (None, None) => f.push_str(".."),
131 (Some(beg), None) => {
132 f.push_str(&beg.to_string());
133 f.push_str("..");
134 }
135 (None, Some(end)) => {
136 f.push_str("..");
137 f.push_str(&end.to_string());
138 }
139 (Some(beg), Some(end)) => {
140 f.push_str(&beg.to_string());
141 f.push_str("..");
142 f.push_str(&end.to_string());
143 }
144 },
145 }
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq)]
152pub enum RecurseInstruction {
153 Path {
154 inclusive: bool,
156 },
157 Collect {
158 inclusive: bool,
160 },
161 Shortest {
162 expects: Expr,
164 inclusive: bool,
166 },
167}
168
169impl ToSql for RecurseInstruction {
170 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
171 match self {
172 Self::Path {
173 inclusive,
174 } => {
175 f.push_str("path");
176
177 if *inclusive {
178 f.push_str("+inclusive");
179 }
180 }
181 Self::Collect {
182 inclusive,
183 } => {
184 f.push_str("collect");
185
186 if *inclusive {
187 f.push_str("+inclusive");
188 }
189 }
190 Self::Shortest {
191 expects,
192 inclusive,
193 } => {
194 write_sql!(f, fmt, "shortest={expects}");
195
196 if *inclusive {
197 f.push_str("+inclusive");
198 }
199 }
200 }
201 }
202}