surrealdb_sql/syn/v1/
mod.rs

1use crate::{err::Error, Subquery};
2use crate::{Datetime, Duration, Idiom, Query, Range, Thing, Value};
3use nom::{Err, Finish};
4
5pub mod literal;
6mod part;
7mod stmt;
8
9mod block;
10pub(crate) mod builtin;
11mod comment;
12mod common;
13mod depth;
14mod ending;
15mod error;
16mod expression;
17mod function;
18mod idiom;
19mod kind;
20mod omit;
21mod operator;
22mod special;
23mod subquery;
24mod thing;
25mod value;
26
27pub use error::{IResult, ParseError};
28
29#[cfg(test)]
30pub(crate) mod test;
31
32fn query(i: &str) -> IResult<&str, Query> {
33	let (i, v) = stmt::statements(i)?;
34	if !i.is_empty() {
35		return Err(Err::Failure(ParseError::ExplainedExpected {
36			tried: i,
37			expected: "query to end",
38			explained: "perhaps missing a semicolon on the previous statement?",
39		}));
40	}
41	Ok((i, Query(v)))
42}
43
44/// Parses a SurrealQL [`Query`]
45///
46/// During query parsing, the total depth of calls to parse values (including arrays, expressions,
47/// functions, objects, sub-queries), Javascript values, and geometry collections count against
48/// a computation depth limit. If the limit is reached, parsing will return
49/// [`Error::ComputationDepthExceeded`], as opposed to spending more time and potentially
50/// overflowing the call stack.
51///
52/// If you encounter this limit and believe that it should be increased,
53/// please [open an issue](https://github.com/surrealdb/surrealdb/issues)!
54#[instrument(level = "debug", name = "parser", skip_all, fields(length = input.len()))]
55pub fn parse(input: &str) -> Result<Query, Error> {
56	parse_impl(input, query)
57}
58
59/// Parses a SurrealQL [`Value`].
60#[instrument(level = "debug", name = "parser", skip_all, fields(length = input.len()))]
61pub fn value(input: &str) -> Result<Value, Error> {
62	parse_impl(input, value::value)
63}
64
65/// Parses JSON into an inert SurrealQL [`Value`]
66#[instrument(level = "debug", name = "parser", skip_all, fields(length = input.len()))]
67pub fn json(input: &str) -> Result<Value, Error> {
68	parse_impl(input, value::json)
69}
70/// Parses a SurrealQL Subquery [`Subquery`]
71#[instrument(level = "debug", name = "parser", skip_all, fields(length = input.len()))]
72pub fn subquery(input: &str) -> Result<Subquery, Error> {
73	parse_impl(input, subquery::subquery)
74}
75
76/// Parses a SurrealQL [`Idiom`]
77#[instrument(level = "debug", name = "parser", skip_all, fields(length = input.len()))]
78pub fn idiom(input: &str) -> Result<Idiom, Error> {
79	parse_impl(input, idiom::plain)
80}
81
82pub fn datetime(input: &str) -> Result<Datetime, Error> {
83	parse_impl(input, literal::datetime)
84}
85
86pub fn datetime_raw(input: &str) -> Result<Datetime, Error> {
87	parse_impl(input, literal::datetime_all_raw)
88}
89
90pub fn duration(input: &str) -> Result<Duration, Error> {
91	parse_impl(input, literal::duration)
92}
93
94pub fn path_like(input: &str) -> Result<Value, Error> {
95	parse_impl(input, value::path_like)
96}
97
98pub fn range(input: &str) -> Result<Range, Error> {
99	parse_impl(input, literal::range)
100}
101
102/// Parses a SurrealQL [`Thing`]
103pub fn thing(input: &str) -> Result<Thing, Error> {
104	parse_impl(input, thing::thing)
105}
106
107pub fn thing_raw(input: &str) -> Result<Thing, Error> {
108	parse_impl(input, thing::thing_raw)
109}
110
111fn parse_impl<O>(input: &str, parser: impl Fn(&str) -> IResult<&str, O>) -> Result<O, Error> {
112	// Reset the parse depth limiter
113	depth::reset();
114
115	// Check the length of the input
116	match input.trim().len() {
117		// The input query was empty
118		0 => Err(Error::QueryEmpty),
119		// Continue parsing the query
120		_ => match parser(input).finish() {
121			// The query was parsed successfully
122			Ok((v, parsed)) if v.is_empty() => Ok(parsed),
123			// There was unparsed SQL remaining
124			Ok((_, _)) => Err(Error::QueryRemaining),
125			// There was an error when parsing the query
126			Err(e) => Err(Error::InvalidQuery(e.render_on(input))),
127		},
128	}
129}
130
131#[cfg(test)]
132mod tests {
133	use super::*;
134	use serde::Serialize;
135	use std::{
136		collections::HashMap,
137		time::{Duration, Instant},
138	};
139
140	#[test]
141	fn no_ending() {
142		let sql = "SELECT * FROM test";
143		parse(sql).unwrap();
144	}
145
146	#[test]
147	fn parse_query_string() {
148		let sql = "SELECT * FROM test;";
149		parse(sql).unwrap();
150	}
151
152	#[test]
153	fn trim_query_string() {
154		let sql = "    SELECT    *    FROM    test    ;    ";
155		parse(sql).unwrap();
156	}
157
158	#[test]
159	fn parse_complex_rubbish() {
160		let sql = "    SELECT    *    FROM    test    ; /* shouldbespace */ ;;;    ";
161		parse(sql).unwrap();
162	}
163
164	#[test]
165	fn parse_complex_failure() {
166		let sql = "    SELECT    *    FROM    { }} ";
167		parse(sql).unwrap_err();
168	}
169
170	#[test]
171	fn parse_ok_recursion() {
172		let sql = "SELECT * FROM ((SELECT * FROM (5))) * 5;";
173		parse(sql).unwrap();
174	}
175
176	#[test]
177	fn parse_ok_recursion_deeper() {
178		let sql = "SELECT * FROM (((( SELECT * FROM ((5)) + ((5)) + ((5)) )))) * ((( function() {return 5;} )));";
179		let start = Instant::now();
180		parse(sql).unwrap();
181		let elapsed = start.elapsed();
182		assert!(
183			elapsed < Duration::from_millis(2000),
184			"took {}ms, previously took ~1000ms in debug",
185			elapsed.as_millis()
186		)
187	}
188
189	#[test]
190	fn parse_recursion_cast() {
191		for n in [10, 100, 500] {
192			recursive("SELECT * FROM ", "<int>", "5", "", n, n > 50);
193		}
194	}
195
196	#[test]
197	fn parse_recursion_geometry() {
198		for n in [1, 50, 100] {
199			recursive(
200				"SELECT * FROM ",
201				r#"{type: "GeometryCollection",geometries: ["#,
202				r#"{type: "MultiPoint",coordinates: [[10.0, 11.2],[10.5, 11.9]]}"#,
203				"]}",
204				n,
205				n > 25,
206			);
207		}
208	}
209
210	#[test]
211	fn parse_recursion_javascript() {
212		for n in [10, 1000] {
213			recursive("SELECT * FROM ", "function() {", "return 5;", "}", n, n > 500);
214		}
215	}
216
217	#[test]
218	fn parse_recursion_mixed() {
219		for n in [3, 15, 75] {
220			recursive("", "SELECT * FROM ((((", "5 * 5", ")))) * 5", n, n > 5);
221		}
222	}
223
224	#[test]
225	fn parse_recursion_select() {
226		for n in [5, 10, 100] {
227			recursive("SELECT * FROM ", "(SELECT * FROM ", "5", ")", n, n > 15);
228		}
229	}
230
231	#[test]
232	fn parse_recursion_value_subquery() {
233		for p in 1..=4 {
234			recursive("SELECT * FROM ", "(", "5", ")", 10usize.pow(p), p > 1);
235		}
236	}
237
238	#[test]
239	fn parse_recursion_if_subquery() {
240		for p in 1..=3 {
241			recursive("SELECT * FROM ", "IF true THEN ", "5", " ELSE 4 END", 6usize.pow(p), p > 1);
242		}
243	}
244
245	#[test]
246	fn parser_try() {
247		let sql = "
248			SELECT
249				*,
250				tags[$].value,
251				3s as duration,
252				1.345 AS number,
253				test AS `some thing`,
254				'2012-04-23T18:25:43.511Z' AS utctime,
255				'2012-04-23T18:25:43.511-08:00' AS pacifictime,
256				{ key: (3 + 1 + 2), other: 9 * 7, 'some thing': { otherkey: 'text', } } AS object
257			FROM $param, test, temp, test:thingy, |test:10|, |test:1..10|
258			WHERE IF true THEN 'YAY' ELSE 'OOPS' END
259				AND (0.1341, 0.5719) INSIDE { type: 'Polygon', coordinates: [[[0.1341, 0.5719], [0.1341, 0.5719]]] }
260				AND (3 + 3 * 4)=6
261				AND 3 + 3 * 4 = 6
262				AND ages CONTAINS 18
263				AND if IS true
264			SPLIT test.things
265			VERSION '2019-01-01T08:00:00Z'
266			TIMEOUT 2w;
267			CREATE person SET name = 'Tobie', age += 18;
268		";
269		let tmp = parse(sql).unwrap();
270
271		let enc: Vec<u8> = Vec::from(&tmp);
272		let dec: Query = Query::from(enc);
273		assert_eq!(tmp, dec);
274	}
275
276	#[test]
277	fn parser_full() {
278		let sql = std::fs::read("test.surql").unwrap();
279		let sql = std::str::from_utf8(&sql).unwrap();
280		let res = parse(sql);
281		let tmp = res.unwrap();
282
283		let enc: Vec<u8> = Vec::from(&tmp);
284		let dec: Query = Query::from(enc);
285		assert_eq!(tmp, dec);
286	}
287
288	#[test]
289	#[cfg_attr(debug_assertions, ignore)]
290	fn json_benchmark() {
291		// From the top level of the repository,
292		// cargo test crate::parser::tests::json_benchmark --package surrealdb --lib --release -- --nocapture --exact
293
294		#[derive(Clone, Serialize)]
295		struct Data {
296			boolean: bool,
297			integer: i32,
298			decimal: f32,
299			string: String,
300			inner: Option<Box<Self>>,
301			inners: Vec<Self>,
302			inner_map: HashMap<String, Self>,
303		}
304
305		let inner = Data {
306			boolean: true,
307			integer: -1,
308			decimal: 0.5,
309			string: "foo".to_owned(),
310			inner: None,
311			inners: Vec::new(),
312			inner_map: HashMap::new(),
313		};
314		let inners = vec![inner.clone(); 10];
315
316		let data = Data {
317			boolean: false,
318			integer: 42,
319			decimal: 9000.0,
320			string: "SurrealDB".to_owned(),
321			inner_map: inners.iter().enumerate().map(|(i, d)| (i.to_string(), d.clone())).collect(),
322			inners,
323			inner: Some(Box::new(inner)),
324		};
325
326		let json = serde_json::to_string(&data).unwrap();
327		let json_pretty = serde_json::to_string_pretty(&data).unwrap();
328
329		let benchmark = |de: fn(&str) -> Value| {
330			let time = Instant::now();
331			const ITERATIONS: u32 = 32;
332			for _ in 0..ITERATIONS {
333				std::hint::black_box(de(std::hint::black_box(&json)));
334				std::hint::black_box(de(std::hint::black_box(&json_pretty)));
335			}
336			time.elapsed().as_secs_f32() / (2 * ITERATIONS) as f32
337		};
338
339		println!("crate::json took {:.10}s/iter", benchmark(|s| crate::json(s).unwrap()));
340	}
341
342	/// Try parsing a query with O(n) recursion depth and expect to fail if and only if
343	/// `excessive` is true.
344	fn recursive(
345		prefix: &str,
346		recursive_start: &str,
347		base: &str,
348		recursive_end: &str,
349		n: usize,
350		excessive: bool,
351	) {
352		let mut sql = String::from(prefix);
353		for _ in 0..n {
354			sql.push_str(recursive_start);
355		}
356		sql.push_str(base);
357		for _ in 0..n {
358			sql.push_str(recursive_end);
359		}
360		let start = Instant::now();
361		let res = query(&sql).finish();
362		let elapsed = start.elapsed();
363		if excessive {
364			assert!(
365				matches!(res, Err(ParseError::ExcessiveDepth(_))),
366				"expected computation depth exceeded, got {:?}",
367				res
368			);
369		} else {
370			res.unwrap();
371		}
372		// The parser can terminate faster in the excessive case.
373		let cutoff = if excessive {
374			500
375		} else {
376			1000
377		};
378		assert!(
379			elapsed < Duration::from_millis(cutoff),
380			"took {}ms, previously much faster to parse {n} in debug mode",
381			elapsed.as_millis()
382		)
383	}
384
385	#[test]
386	fn single_query() {
387		let sql = "CREATE test";
388		let res = query(sql);
389		assert!(res.is_ok());
390		let out = res.unwrap().1;
391		assert_eq!("CREATE test;", format!("{}", out))
392	}
393
394	#[test]
395	fn multiple_query() {
396		let sql = "CREATE test; CREATE temp;";
397		let res = query(sql);
398		assert!(res.is_ok());
399		let out = res.unwrap().1;
400		assert_eq!("CREATE test;\nCREATE temp;", format!("{}", out))
401	}
402
403	#[test]
404	fn multiple_query_semicolons() {
405		let sql = "CREATE test;;;CREATE temp;;;";
406		let res = query(sql);
407		assert!(res.is_ok());
408		let out = res.unwrap().1;
409		assert_eq!("CREATE test;\nCREATE temp;", format!("{}", out))
410	}
411
412	#[test]
413	fn multiple_query_semicolons_comments() {
414		let sql = "CREATE test;;;CREATE temp;;;/* some comment */";
415		let res = query(sql);
416		assert!(res.is_ok());
417		let out = res.unwrap().1;
418		assert_eq!("CREATE test;\nCREATE temp;", format!("{}", out))
419	}
420
421	#[test]
422	fn multiple_query_semicolons_multi_comments() {
423		let sql = "CREATE test;;;CREATE temp;;;/* some comment */;;;/* other comment */";
424		let res = query(sql);
425		assert!(res.is_ok());
426		let out = res.unwrap().1;
427		assert_eq!("CREATE test;\nCREATE temp;", format!("{}", out))
428	}
429}