1use std::ops::Bound;
2
3use common::fmt::{EscapeKwFreeIdent, Fmt};
4use surrealdb_types::{SqlFormat, ToSql, write_sql};
5
6use crate::order::Ordering;
7use crate::{
8 Cond, Dir, Fields, Groups, Idiom, Limit, RecordIdKeyRangeLit, Splits, Start, TableName,
9};
10
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
14pub struct Lookup {
15 pub kind: LookupKind,
16 pub expr: Option<Fields>,
17 pub only: bool,
18 pub what: Vec<LookupSubject>,
19 pub cond: Option<Cond>,
20 pub split: Option<Splits>,
21 pub group: Option<Groups>,
22 pub order: Option<Ordering>,
23 pub limit: Option<Limit>,
24 pub start: Option<Start>,
25 pub alias: Option<Idiom>,
26}
27
28impl ToSql for Lookup {
29 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
30 if self.what.len() <= 1
31 && self.what.iter().all(|v| {
38 if v.referencing_field().is_some() {
39 return false
40 }
41 if let LookupSubject::Range { range: RecordIdKeyRangeLit{ end: Bound::Unbounded, .. }, ..} = v {
42 return false
43 }
44 true
45 })
46 && self.cond.is_none()
47 && self.alias.is_none()
48 && self.expr.is_none()
49 {
50 self.kind.fmt_sql(f, fmt);
51 if self.what.is_empty() {
52 f.push('?');
53 } else {
54 write_sql!(f, fmt, "{}", Fmt::comma_separated(self.what.iter()));
55 }
56 } else {
57 write_sql!(f, fmt, "{}(", self.kind);
58 if let Some(ref expr) = self.expr {
59 write_sql!(f, fmt, "SELECT {} FROM ", expr);
60 if self.only {
61 f.push_str("ONLY ");
62 }
63 }
64 if self.what.is_empty() {
65 f.push('?');
66 } else {
67 write_sql!(f, fmt, "{}", Fmt::comma_separated(&self.what));
68 }
69 if let Some(ref v) = self.cond {
70 write_sql!(f, fmt, " {v}");
71 }
72 if let Some(ref v) = self.split {
73 write_sql!(f, fmt, " {v}");
74 }
75 if let Some(ref v) = self.group {
76 write_sql!(f, fmt, " {v}");
77 }
78 if let Some(ref v) = self.order {
79 write_sql!(f, fmt, " {v}");
80 }
81 if let Some(ref v) = self.limit {
82 write_sql!(f, fmt, " {v}");
83 }
84 if let Some(ref v) = self.start {
85 write_sql!(f, fmt, " {v}");
86 }
87 if let Some(ref v) = self.alias {
88 write_sql!(f, fmt, " AS {v}");
89 }
90 f.push(')');
91 }
92 }
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
98pub enum LookupKind {
99 Graph(Dir),
100 Reference,
101}
102
103impl Default for LookupKind {
104 fn default() -> Self {
105 Self::Graph(Dir::Both)
106 }
107}
108
109impl ToSql for LookupKind {
110 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
111 match self {
112 Self::Graph(dir) => dir.fmt_sql(f, fmt),
113 Self::Reference => f.push_str("<~"),
114 }
115 }
116}
117
118#[derive(Clone, Debug, PartialEq, Eq)]
120#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
121pub enum LookupSubject {
122 Table {
123 table: TableName,
124 referencing_field: Option<String>,
125 },
126 Range {
127 table: TableName,
128 range: RecordIdKeyRangeLit,
129 referencing_field: Option<String>,
130 },
131}
132
133impl LookupSubject {
134 pub fn referencing_field(&self) -> Option<&String> {
135 match self {
136 LookupSubject::Table {
137 referencing_field,
138 ..
139 } => referencing_field.as_ref(),
140 LookupSubject::Range {
141 referencing_field,
142 ..
143 } => referencing_field.as_ref(),
144 }
145 }
146}
147
148impl ToSql for LookupSubject {
149 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
150 match self {
151 Self::Table {
152 table,
153 referencing_field,
154 } => {
155 write_sql!(f, fmt, "{}", EscapeKwFreeIdent(table.as_str()));
156 if let Some(referencing_field) = referencing_field {
157 write_sql!(f, fmt, " FIELD {}", EscapeKwFreeIdent(referencing_field));
158 }
159 }
160 Self::Range {
161 table,
162 range,
163 referencing_field,
164 } => {
165 write_sql!(f, fmt, "{}:{range}", EscapeKwFreeIdent(table.as_str()));
166 if let Some(referencing_field) = referencing_field {
167 write_sql!(f, fmt, " FIELD {}", EscapeKwFreeIdent(referencing_field));
168 }
169 }
170 }
171 }
172}