Skip to main content

surql_parser/upstream/sql/
literal.rs

1use crate::compat::types::{
2	PublicBytes, PublicDatetime, PublicDuration, PublicFile, PublicGeometry, PublicRegex,
3	PublicUuid,
4};
5use crate::compat::val::Geometry;
6use crate::upstream::fmt::{CoverStmts, EscapeObjectKey, Float, QuoteStr};
7use crate::upstream::sql::{Expr, RecordIdLit};
8use geo::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
9use rust_decimal::Decimal;
10use surrealdb_types::{SqlFormat, ToSql, write_sql};
11#[derive(Clone, Debug)]
12#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
13pub enum Literal {
14	None,
15	Null,
16	UnboundedRange,
17	Bool(bool),
18	Float(f64),
19	Integer(i64),
20	Decimal(
21		#[cfg_attr(
22            feature = "arbitrary",
23            arbitrary(with = crate::upstream::sql::arbitrary::arb_decimal)
24        )]
25		Decimal,
26	),
27	Duration(PublicDuration),
28	String(String),
29	RecordId(RecordIdLit),
30	Datetime(PublicDatetime),
31	Uuid(PublicUuid),
32	Regex(PublicRegex),
33	Array(Vec<Expr>),
34	Set(Vec<Expr>),
35	Object(Vec<ObjectEntry>),
36	Geometry(PublicGeometry),
37	File(PublicFile),
38	Bytes(PublicBytes),
39}
40impl PartialEq for Literal {
41	fn eq(&self, other: &Self) -> bool {
42		match (self, other) {
43			(Literal::None, Literal::None) => true,
44			(Literal::Null, Literal::Null) => true,
45			(Literal::Bool(a), Literal::Bool(b)) => a == b,
46			(Literal::Float(a), Literal::Float(b)) => a.to_bits() == b.to_bits(),
47			(Literal::Integer(a), Literal::Integer(b)) => a == b,
48			(Literal::Decimal(a), Literal::Decimal(b)) => a == b,
49			(Literal::String(a), Literal::String(b)) => a == b,
50			(Literal::Bytes(a), Literal::Bytes(b)) => a == b,
51			(Literal::Regex(a), Literal::Regex(b)) => a == b,
52			(Literal::RecordId(a), Literal::RecordId(b)) => a == b,
53			(Literal::Array(a), Literal::Array(b)) => a == b,
54			(Literal::Set(a), Literal::Set(b)) => a == b,
55			(Literal::Object(a), Literal::Object(b)) => a == b,
56			(Literal::Duration(a), Literal::Duration(b)) => a == b,
57			(Literal::Datetime(a), Literal::Datetime(b)) => a == b,
58			(Literal::Uuid(a), Literal::Uuid(b)) => a == b,
59			(Literal::Geometry(a), Literal::Geometry(b)) => a == b,
60			(Literal::File(a), Literal::File(b)) => a == b,
61			_ => false,
62		}
63	}
64}
65impl Eq for Literal {}
66impl ToSql for Literal {
67	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
68		match self {
69			Literal::None => f.push_str("NONE"),
70			Literal::Null => f.push_str("NULL"),
71			Literal::UnboundedRange => f.push_str(".."),
72			Literal::Bool(x) => {
73				if *x {
74					f.push_str("true");
75				} else {
76					f.push_str("false");
77				}
78			}
79			Literal::Float(float) => write_sql!(f, fmt, "{}", Float(*float)),
80			Literal::Integer(x) => f.push_str(&x.to_string()),
81			Literal::Decimal(d) => d.fmt_sql(f, fmt),
82			Literal::String(strand) => write_sql!(f, fmt, "{}", QuoteStr(strand)),
83			Literal::Bytes(bytes) => bytes.fmt_sql(f, fmt),
84			Literal::Regex(regex) => regex.fmt_sql(f, fmt),
85			Literal::RecordId(record_id_lit) => record_id_lit.fmt_sql(f, fmt),
86			Literal::Array(exprs) => {
87				f.push('[');
88				if !exprs.is_empty() {
89					let fmt = fmt.increment();
90					if fmt.is_pretty() {
91						f.push('\n');
92						fmt.write_indent(f);
93					}
94					for (i, expr) in exprs.iter().enumerate() {
95						if i > 0 {
96							fmt.write_separator(f);
97						}
98						CoverStmts(expr).fmt_sql(f, fmt);
99					}
100					if fmt.is_pretty() {
101						f.push('\n');
102						if let SqlFormat::Indented(level) = fmt
103							&& level > 0
104						{
105							for _ in 0..(level - 1) {
106								f.push('\t');
107							}
108						}
109					}
110				}
111				f.push(']');
112			}
113			Literal::Set(exprs) => {
114				f.push('{');
115				if !exprs.is_empty() {
116					let fmt = fmt.increment();
117					if fmt.is_pretty() {
118						f.push('\n');
119						fmt.write_indent(f);
120					}
121					for (i, expr) in exprs.iter().enumerate() {
122						if i > 0 {
123							fmt.write_separator(f);
124						} else if let Expr::Literal(Literal::RecordId(_)) = *expr {
125							f.push('(');
126							expr.fmt_sql(f, fmt);
127							f.push(')');
128							continue;
129						}
130						CoverStmts(expr).fmt_sql(f, fmt);
131					}
132					if exprs.len() == 1 {
133						f.push(',');
134					}
135					if fmt.is_pretty() {
136						f.push('\n');
137						if let SqlFormat::Indented(level) = fmt
138							&& level > 0
139						{
140							for _ in 0..(level - 1) {
141								f.push('\t');
142							}
143						}
144					}
145				} else {
146					f.push(',');
147				}
148				f.push('}');
149			}
150			Literal::Object(items) => {
151				if fmt.is_pretty() {
152					f.push('{');
153				} else {
154					f.push_str("{ ");
155				}
156				if !items.is_empty() {
157					let fmt = fmt.increment();
158					if fmt.is_pretty() {
159						f.push('\n');
160						fmt.write_indent(f);
161					}
162					for (i, entry) in items.iter().enumerate() {
163						if i > 0 {
164							fmt.write_separator(f);
165						}
166						write_sql!(
167							f,
168							fmt,
169							"{}: {}",
170							EscapeObjectKey(&entry.key),
171							CoverStmts(&entry.value)
172						);
173					}
174					if fmt.is_pretty() {
175						f.push('\n');
176						if let SqlFormat::Indented(level) = fmt
177							&& level > 0
178						{
179							for _ in 0..(level - 1) {
180								f.push('\t');
181							}
182						}
183					}
184				}
185				if fmt.is_pretty() {
186					f.push('}');
187				} else {
188					f.push_str(" }");
189				}
190			}
191			Literal::Duration(duration) => duration.fmt_sql(f, fmt),
192			Literal::Datetime(datetime) => {
193				f.push('d');
194				write_sql!(f, fmt, "{}", QuoteStr(&datetime.to_string()));
195			}
196			Literal::Uuid(uuid) => uuid.fmt_sql(f, fmt),
197			Literal::Geometry(geometry) => geometry.fmt_sql(f, fmt),
198			Literal::File(file) => file.fmt_sql(f, fmt),
199		}
200	}
201}
202/// A hack to convert objects to geometries like they previously would.
203/// If it fails to convert to geometry it just returns an object like previous
204/// behaviour>
205///
206/// The behaviour around geometries needs to be improved but until then this is
207/// her to ensure they still work like they previously would.
208fn collect_geometry(map: &[ObjectEntry]) -> Option<Geometry> {
209	if map.len() != 2 {
210		return None;
211	}
212	let ty_idx = map.iter().position(|x| x.key == "type")?;
213	let other = 1 ^ ty_idx;
214	let Expr::Literal(Literal::String(ty)) = &map[ty_idx].value else {
215		return None;
216	};
217	match ty.as_str() {
218		"Point" => {
219			let other = &map[other];
220			if other.key != "coordinates" {
221				return None;
222			}
223			let geom = collect_point(&other.value)?;
224			Some(Geometry::Point(geom))
225		}
226		"LineString" => {
227			let other = &map[other];
228			if other.key != "coordinates" {
229				return None;
230			}
231			let geom = collect_array(&other.value, collect_point)?;
232			Some(Geometry::Line(LineString::from(geom)))
233		}
234		"Polygon" => {
235			let other = &map[other];
236			if other.key != "coordinates" {
237				return None;
238			}
239			let geom = collect_polygon(&other.value)?;
240			Some(Geometry::Polygon(geom))
241		}
242		"MultiPoint" => {
243			let other = &map[other];
244			if other.key != "coordinates" {
245				return None;
246			}
247			let geom = collect_array(&other.value, collect_point)?;
248			Some(Geometry::MultiPoint(MultiPoint::new(geom)))
249		}
250		"MultiLineString" => {
251			let other = &map[other];
252			if other.key != "coordinates" {
253				return None;
254			}
255			let geom = collect_array(&other.value, |x| {
256				collect_array(x, collect_point).map(LineString::from)
257			})?;
258			Some(Geometry::MultiLine(MultiLineString::new(geom)))
259		}
260		"MultiPolygon" => {
261			let other = &map[other];
262			if other.key != "coordinates" {
263				return None;
264			}
265			let geom = collect_array(&other.value, collect_polygon)?;
266			Some(Geometry::MultiPolygon(MultiPolygon::new(geom)))
267		}
268		"GeometryCollection" => {
269			let other = &map[other];
270			if other.key != "geometries" {
271				return None;
272			}
273			let geom = collect_array(&other.value, |x| {
274				let Expr::Literal(Literal::Object(x)) = x else {
275					return None;
276				};
277				collect_geometry(x)
278			})?;
279			Some(Geometry::Collection(geom))
280		}
281		_ => None,
282	}
283}
284fn collect_polygon(expr: &Expr) -> Option<Polygon<f64>> {
285	let Expr::Literal(Literal::Array(x)) = expr else {
286		return None;
287	};
288	if x.is_empty() {
289		return None;
290	}
291	let first = LineString::from(collect_array(&x[0], collect_point)?);
292	let mut res = Vec::new();
293	for x in &x[1..] {
294		res.push(LineString::from(collect_array(x, collect_point)?))
295	}
296	Some(Polygon::new(first, res))
297}
298fn collect_point(expr: &Expr) -> Option<Point<f64>> {
299	let Expr::Literal(Literal::Array(array)) = expr else {
300		return None;
301	};
302	if array.len() != 2 {
303		return None;
304	}
305	let x = collect_number(&array[0])?;
306	let y = collect_number(&array[1])?;
307	Some(Point::new(x, y))
308}
309fn collect_number(expr: &Expr) -> Option<f64> {
310	let Expr::Literal(l) = expr else {
311		return None;
312	};
313	match l {
314		Literal::Integer(x) => Some(*x as f64),
315		Literal::Float(f) => Some(*f),
316		Literal::Decimal(_) => None,
317		_ => None,
318	}
319}
320fn collect_array<R, F: Fn(&Expr) -> Option<R>>(expr: &Expr, f: F) -> Option<Vec<R>> {
321	let Expr::Literal(Literal::Array(x)) = expr else {
322		return None;
323	};
324	x.iter().map(f).collect()
325}
326#[derive(Clone, Debug, Eq, PartialEq)]
327#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
328pub struct ObjectEntry {
329	pub key: String,
330	pub value: Expr,
331}
332impl ToSql for ObjectEntry {
333	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
334		write_sql!(f, fmt, "{}: {}", EscapeObjectKey(&self.key), self.value);
335	}
336}