Skip to main content

surrealdb_sql/
part.rs

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// ------------------------------
85
86#[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// ------------------------------
117
118#[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// ------------------------------
150
151#[derive(Clone, Debug, PartialEq, Eq)]
152pub enum RecurseInstruction {
153	Path {
154		// Do we include the starting point in the paths?
155		inclusive: bool,
156	},
157	Collect {
158		// Do we include the starting point in the collection?
159		inclusive: bool,
160	},
161	Shortest {
162		// What ending node are we looking for?
163		expects: Expr,
164		// Do we include the starting point in the collection?
165		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}