Skip to main content

surql_parser/upstream/sql/
lookup.rs

1use crate::upstream::fmt::{EscapeKwFreeIdent, Fmt};
2use crate::upstream::sql::order::Ordering;
3use crate::upstream::sql::{
4	Cond, Dir, Fields, Groups, Idiom, Limit, RecordIdKeyRangeLit, Splits, Start,
5};
6use std::ops::Bound;
7use surrealdb_types::{SqlFormat, ToSql, write_sql};
8/// A lookup is a unified way of looking up graph edges and record references.
9/// Since they both work very similarly, they also both support the same operations
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct Lookup {
12	pub kind: LookupKind,
13	pub expr: Option<Fields>,
14	pub what: Vec<LookupSubject>,
15	pub cond: Option<Cond>,
16	pub split: Option<Splits>,
17	pub group: Option<Groups>,
18	pub order: Option<Ordering>,
19	pub limit: Option<Limit>,
20	pub start: Option<Start>,
21	pub alias: Option<Idiom>,
22}
23impl ToSql for Lookup {
24	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
25		if self.what.len() <= 1
26			&& self.what.iter().all(|v| {
27				if v.referencing_field().is_some() {
28					return false;
29				}
30				if let LookupSubject::Range {
31					range: RecordIdKeyRangeLit {
32						end: Bound::Unbounded,
33						..
34					},
35					..
36				} = v
37				{
38					return false;
39				}
40				true
41			}) && self.cond.is_none()
42			&& self.alias.is_none()
43			&& self.expr.is_none()
44		{
45			self.kind.fmt_sql(f, fmt);
46			if self.what.is_empty() {
47				f.push('?');
48			} else {
49				write_sql!(f, fmt, "{}", Fmt::comma_separated(self.what.iter()));
50			}
51		} else {
52			write_sql!(f, fmt, "{}(", self.kind);
53			if let Some(ref expr) = self.expr {
54				write_sql!(f, fmt, "SELECT {} FROM ", expr);
55			}
56			if self.what.is_empty() {
57				f.push('?');
58			} else {
59				write_sql!(f, fmt, "{}", Fmt::comma_separated(&self.what));
60			}
61			if let Some(ref v) = self.cond {
62				write_sql!(f, fmt, " {v}");
63			}
64			if let Some(ref v) = self.split {
65				write_sql!(f, fmt, " {v}");
66			}
67			if let Some(ref v) = self.group {
68				write_sql!(f, fmt, " {v}");
69			}
70			if let Some(ref v) = self.order {
71				write_sql!(f, fmt, " {v}");
72			}
73			if let Some(ref v) = self.limit {
74				write_sql!(f, fmt, " {v}");
75			}
76			if let Some(ref v) = self.start {
77				write_sql!(f, fmt, " {v}");
78			}
79			if let Some(ref v) = self.alias {
80				write_sql!(f, fmt, " AS {v}");
81			}
82			f.push(')');
83		}
84	}
85}
86/// This enum instructs whether the lookup is a graph edge or a record reference
87#[derive(Clone, Debug, PartialEq, Eq)]
88#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
89pub enum LookupKind {
90	Graph(Dir),
91	Reference,
92}
93impl Default for LookupKind {
94	fn default() -> Self {
95		Self::Graph(Dir::Both)
96	}
97}
98impl ToSql for LookupKind {
99	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
100		match self {
101			Self::Graph(dir) => dir.fmt_sql(f, fmt),
102			Self::Reference => f.push_str("<~"),
103		}
104	}
105}
106/// This enum instructs whether we scan all edges on a table or just a specific range
107#[derive(Clone, Debug, PartialEq, Eq)]
108#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
109pub enum LookupSubject {
110	Table {
111		table: String,
112		referencing_field: Option<String>,
113	},
114	Range {
115		table: String,
116		range: RecordIdKeyRangeLit,
117		referencing_field: Option<String>,
118	},
119}
120impl LookupSubject {
121	pub fn referencing_field(&self) -> Option<&String> {
122		match self {
123			LookupSubject::Table {
124				referencing_field, ..
125			} => referencing_field.as_ref(),
126			LookupSubject::Range {
127				referencing_field, ..
128			} => referencing_field.as_ref(),
129		}
130	}
131}
132impl ToSql for LookupSubject {
133	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
134		match self {
135			Self::Table {
136				table,
137				referencing_field,
138			} => {
139				write_sql!(f, fmt, "{}", EscapeKwFreeIdent(table));
140				if let Some(referencing_field) = referencing_field {
141					write_sql!(f, fmt, " FIELD {}", EscapeKwFreeIdent(referencing_field));
142				}
143			}
144			Self::Range {
145				table,
146				range,
147				referencing_field,
148			} => {
149				write_sql!(f, fmt, "{}:{range}", EscapeKwFreeIdent(table));
150				if let Some(referencing_field) = referencing_field {
151					write_sql!(f, fmt, " FIELD {}", EscapeKwFreeIdent(referencing_field));
152				}
153			}
154		}
155	}
156}