Skip to main content

surrealdb_sql/
literal.rs

1//use async_graphql::dynamic::Object;
2use chrono::{DateTime, Utc};
3use common::fmt::{EscapeObjectKey, Float, QuoteStr, SqlDatetime, SqlDuration};
4use rust_decimal::Decimal;
5use surrealdb_strand::Strand;
6use surrealdb_types::{SqlFormat, ToSql, write_sql};
7
8use crate::file::File;
9use crate::{CoverStmts, Expr, RecordIdLit};
10
11#[derive(Clone, Debug)]
12#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
13pub enum Literal {
14	None,
15	Null,
16	// and unbounded range: `..`
17	UnboundedRange,
18	Bool(bool),
19	Float(f64),
20	Integer(i64),
21	Decimal(
22		#[cfg_attr(feature = "arbitrary", arbitrary(with = crate::arbitrary::arb_decimal))] Decimal,
23	),
24	Duration(std::time::Duration),
25
26	String(Strand),
27	RecordId(RecordIdLit),
28	Datetime(DateTime<Utc>),
29	Uuid(uuid::Uuid),
30	Regex(
31		#[cfg_attr(feature = "arbitrary", arbitrary(with = crate::arbitrary::arb_regex))]
32		regex::Regex,
33	),
34
35	Array(Vec<Expr>),
36	Set(Vec<Expr>),
37	Object(Vec<ObjectEntry>),
38	Geometry(geo::Geometry<f64>),
39	File(File),
40	Bytes(
41		#[cfg_attr(feature = "arbitrary", arbitrary(with = crate::arbitrary::arb_bytes))]
42		bytes::Bytes,
43	),
44}
45
46impl PartialEq for Literal {
47	fn eq(&self, other: &Self) -> bool {
48		match (self, other) {
49			(Literal::None, Literal::None) => true,
50			(Literal::Null, Literal::Null) => true,
51			(Literal::Bool(a), Literal::Bool(b)) => a == b,
52			(Literal::Float(a), Literal::Float(b)) => a.to_bits() == b.to_bits(),
53			(Literal::Integer(a), Literal::Integer(b)) => a == b,
54			(Literal::Decimal(a), Literal::Decimal(b)) => a == b,
55			(Literal::String(a), Literal::String(b)) => a == b,
56			(Literal::Bytes(a), Literal::Bytes(b)) => a == b,
57			// `regex::Regex` has no `PartialEq`; compare the source pattern instead.
58			(Literal::Regex(a), Literal::Regex(b)) => a.as_str() == b.as_str(),
59			(Literal::RecordId(a), Literal::RecordId(b)) => a == b,
60			(Literal::Array(a), Literal::Array(b)) => a == b,
61			(Literal::Set(a), Literal::Set(b)) => a == b,
62			(Literal::Object(a), Literal::Object(b)) => a == b,
63			(Literal::Duration(a), Literal::Duration(b)) => a == b,
64			(Literal::Datetime(a), Literal::Datetime(b)) => a == b,
65			(Literal::Uuid(a), Literal::Uuid(b)) => a == b,
66			(Literal::Geometry(a), Literal::Geometry(b)) => a == b,
67			(Literal::File(a), Literal::File(b)) => a == b,
68			// Payload-free, so equal by construction. Without this arm it falls
69			// to `_ => false` and `impl Eq` below asserts a reflexivity that
70			// does not hold.
71			(Literal::UnboundedRange, Literal::UnboundedRange) => true,
72			// Deliberately last, and deliberately not a catch-all for variants
73			// that were simply forgotten: every variant needs an arm above.
74			// `every_variant_equals_itself` is what enforces that.
75			_ => false,
76		}
77	}
78}
79impl Eq for Literal {}
80
81impl ToSql for Literal {
82	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
83		match self {
84			Literal::None => f.push_str("NONE"),
85			Literal::Null => f.push_str("NULL"),
86			Literal::UnboundedRange => f.push_str(".."),
87			Literal::Bool(x) => {
88				if *x {
89					f.push_str("true");
90				} else {
91					f.push_str("false");
92				}
93			}
94			Literal::Float(float) => write_sql!(f, fmt, "{}", Float(*float)),
95			Literal::Integer(x) => f.push_str(&x.to_string()),
96			Literal::Decimal(d) => d.fmt_sql(f, fmt),
97			Literal::String(strand) => write_sql!(f, fmt, "{}", QuoteStr(strand)),
98			Literal::Bytes(bytes) => write_sql!(f, fmt, "b\"{}\"", hex::encode_upper(bytes)),
99			Literal::Regex(regex) => {
100				let pattern = regex.to_string().replace('/', "\\/");
101				write_sql!(f, fmt, "/{}/", &pattern);
102			}
103			Literal::RecordId(record_id_lit) => record_id_lit.fmt_sql(f, fmt),
104			Literal::Array(exprs) => {
105				f.push('[');
106				if !exprs.is_empty() {
107					let fmt = fmt.increment();
108					if fmt.is_pretty() {
109						f.push('\n');
110						fmt.write_indent(f);
111					}
112					for (i, expr) in exprs.iter().enumerate() {
113						if i > 0 {
114							fmt.write_separator(f);
115						}
116						CoverStmts(expr).fmt_sql(f, fmt);
117					}
118					if fmt.is_pretty() {
119						f.push('\n');
120						// One level less indentation for closing bracket
121						if let SqlFormat::Indented(level) = fmt
122							&& level > 0
123						{
124							for _ in 0..(level - 1) {
125								f.push('\t');
126							}
127						}
128					}
129				}
130				f.push(']');
131			}
132			Literal::Set(exprs) => {
133				f.push('{');
134				if !exprs.is_empty() {
135					let fmt = fmt.increment();
136					if fmt.is_pretty() {
137						f.push('\n');
138						fmt.write_indent(f);
139					}
140					for (i, expr) in exprs.iter().enumerate() {
141						if i > 0 {
142							fmt.write_separator(f);
143						} else if let Expr::Literal(Literal::RecordId(_)) = *expr {
144							f.push('(');
145							expr.fmt_sql(f, fmt);
146							f.push(')');
147							continue;
148						}
149						CoverStmts(expr).fmt_sql(f, fmt);
150					}
151
152					if exprs.len() == 1 {
153						f.push(',');
154					}
155
156					if fmt.is_pretty() {
157						f.push('\n');
158						// One level less indentation for closing bracket
159						if let SqlFormat::Indented(level) = fmt
160							&& level > 0
161						{
162							for _ in 0..(level - 1) {
163								f.push('\t');
164							}
165						}
166					}
167				} else {
168					f.push(',');
169				}
170				f.push('}');
171			}
172			Literal::Object(items) => {
173				if fmt.is_pretty() {
174					f.push('{');
175				} else {
176					f.push_str("{ ");
177				}
178				if !items.is_empty() {
179					let fmt = fmt.increment();
180					if fmt.is_pretty() {
181						f.push('\n');
182						fmt.write_indent(f);
183					}
184					for (i, entry) in items.iter().enumerate() {
185						if i > 0 {
186							fmt.write_separator(f);
187						}
188						write_sql!(
189							f,
190							fmt,
191							"{}: {}",
192							EscapeObjectKey(&entry.key),
193							CoverStmts(&entry.value)
194						);
195					}
196					if fmt.is_pretty() {
197						f.push('\n');
198						// One level less indentation for closing bracket
199						if let SqlFormat::Indented(level) = fmt
200							&& level > 0
201						{
202							for _ in 0..(level - 1) {
203								f.push('\t');
204							}
205						}
206					}
207				}
208				if fmt.is_pretty() {
209					f.push('}');
210				} else {
211					f.push_str(" }");
212				}
213			}
214			Literal::Duration(duration) => SqlDuration(*duration).fmt_sql(f, fmt),
215			Literal::Datetime(datetime) => SqlDatetime(*datetime).fmt_sql(f, fmt),
216			Literal::Uuid(uuid) => write_sql!(f, fmt, "u{}", QuoteStr(&uuid.to_string())),
217			// Delegate to the public Geometry's rendering: float coordinates get
218			// the `f` suffix there, unlike the runtime Geometry's bare-f64
219			// printing. Cold, AST-printing path, not the exec hot path.
220			Literal::Geometry(geometry) => {
221				surrealdb_types::Geometry::from(geometry.clone()).fmt_sql(f, fmt)
222			}
223			Literal::File(file) => file.fmt_sql(f, fmt),
224		}
225	}
226}
227
228/// A hack to convert objects to geometries like they previously would.
229/// If it fails to convert to geometry it just returns an object like previous
230/// behaviour>
231///
232/// The behaviour around geometries needs to be improved but until then this is
233/// her to ensure they still work like they previously would.
234
235#[derive(Clone, Debug, Eq, PartialEq)]
236#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
237pub struct ObjectEntry {
238	pub key: Strand,
239	pub value: Expr,
240}
241
242impl ToSql for ObjectEntry {
243	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
244		write_sql!(f, fmt, "{}: {}", EscapeObjectKey(self.key.as_str()), self.value);
245	}
246}
247
248#[cfg(test)]
249mod tests {
250	use super::*;
251
252	/// One value per variant.
253	///
254	/// The exhaustive match is the point: it makes adding a variant a compile
255	/// error here, so the sample list cannot silently fall behind the enum the
256	/// way a hand-maintained list does.
257	fn one_of_each() -> Vec<Literal> {
258		fn _forces_this_list_to_be_updated(l: &Literal) {
259			match l {
260				Literal::None
261				| Literal::Null
262				| Literal::UnboundedRange
263				| Literal::Bool(_)
264				| Literal::Float(_)
265				| Literal::Integer(_)
266				| Literal::Decimal(_)
267				| Literal::Duration(_)
268				| Literal::String(_)
269				| Literal::RecordId(_)
270				| Literal::Datetime(_)
271				| Literal::Uuid(_)
272				| Literal::Regex(_)
273				| Literal::Array(_)
274				| Literal::Set(_)
275				| Literal::Object(_)
276				| Literal::Geometry(_)
277				| Literal::File(_)
278				| Literal::Bytes(_) => {}
279			}
280		}
281		vec![
282			Literal::None,
283			Literal::Null,
284			Literal::UnboundedRange,
285			Literal::Bool(true),
286			Literal::Float(1.5),
287			Literal::Integer(7),
288			Literal::Decimal(Decimal::new(150, 2)),
289			Literal::Duration(std::time::Duration::from_secs(1)),
290			Literal::String(Strand::new("s")),
291			Literal::RecordId(RecordIdLit {
292				table: "t".into(),
293				key: crate::RecordIdKeyLit::Number(1),
294			}),
295			Literal::Datetime(DateTime::<Utc>::from_timestamp(0, 0).unwrap()),
296			Literal::Uuid(uuid::Uuid::nil()),
297			Literal::Regex("a".parse().unwrap()),
298			Literal::Array(vec![]),
299			Literal::Set(vec![]),
300			Literal::Object(vec![]),
301			Literal::Geometry(geo::Geometry::Point(geo::Point::new(1.0, 2.0))),
302			Literal::File(File {
303				bucket: "b".to_owned(),
304				key: "p".to_owned(),
305			}),
306			Literal::Bytes(bytes::Bytes::from_static(b"x")),
307		]
308	}
309
310	/// Every variant must equal itself.
311	///
312	/// `PartialEq` is hand-written with a `_ => false` fallthrough, so a variant
313	/// omitted from it compares unequal to itself rather than failing to
314	/// compile — while `impl Eq` asserts the opposite. `sql::Expr` derives its
315	/// equality from this, and `sql::Ast` from that, so a single missing arm
316	/// makes structural comparison of any parsed query containing that literal
317	/// silently false.
318	#[test]
319	fn every_variant_equals_itself() {
320		for l in one_of_each() {
321			assert_eq!(l, l.clone(), "{l:?} must equal itself");
322		}
323	}
324
325	/// The two zeroes render differently (`-0f` vs `0f`), so treating them as
326	/// equal would let a change-detector miss a real edit.
327	#[test]
328	fn signed_zero_is_not_equal_to_zero() {
329		assert_ne!(Literal::Float(0.0), Literal::Float(-0.0));
330	}
331}