1use 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 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 (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 (Literal::UnboundedRange, Literal::UnboundedRange) => true,
72 _ => 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 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 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 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 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#[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 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 #[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 #[test]
328 fn signed_zero_is_not_equal_to_zero() {
329 assert_ne!(Literal::Float(0.0), Literal::Float(-0.0));
330 }
331}