Skip to main content

surql_parser/upstream/syn/
mod.rs

1//! Module containing the implementation of the surrealql tokens, lexer, and
2//! parser.
3use crate::compat::Capabilities;
4use crate::compat::capabilities::ExperimentalTarget;
5use crate::compat::err::Error;
6use crate::compat::types::{PublicDatetime, PublicDuration, PublicRecordId, PublicValue};
7use crate::config::{MAX_OBJECT_PARSING_DEPTH, MAX_QUERY_PARSING_DEPTH};
8use crate::upstream::sql::kind::KindLiteral;
9use crate::upstream::sql::{Ast, Block, Expr, Function, Idiom, Kind};
10use std::collections::HashSet;
11pub mod error;
12pub mod lexer;
13pub mod parser;
14pub mod token;
15#[cfg(test)]
16pub trait Parse<T> {
17	fn parse(val: &str) -> T;
18}
19use anyhow::{Result, bail, ensure};
20use lexer::Lexer;
21pub use parser::ParserSettings;
22use parser::{ParseResult, Parser};
23use reblessive::{Stack, Stk};
24use token::t;
25const TARGET: &str = "surrealdb::core::syn";
26/// Takes a string and returns if it could be a reserved keyword in certain
27/// contexts.
28pub fn could_be_reserved_keyword(s: &str) -> bool {
29	lexer::keywords::could_be_reserved(s)
30}
31pub fn parse_with<F, R>(input: &[u8], f: F) -> Result<R>
32where
33	F: AsyncFnOnce(&mut Parser<'_>, &mut Stk) -> ParseResult<R>,
34{
35	parse_with_settings(input, settings_from_capabilities(&Capabilities::all()), f)
36}
37pub fn parse_with_settings<F, R>(input: &[u8], settings: ParserSettings, f: F) -> Result<R>
38where
39	F: for<'a> AsyncFnOnce(&'a mut Parser<'a>, &'a mut Stk) -> ParseResult<R>,
40{
41	ensure!(input.len() <= u32::MAX as usize, Error::QueryTooLarge);
42	let mut parser = Parser::new_with_settings(input, settings);
43	let mut stack = Stack::new();
44	stack
45		.enter(|stk| f(&mut parser, stk))
46		.finish()
47		.map_err(|e| e.render_on_bytes(input))
48		.map_err(Error::InvalidQuery)
49		.map_err(anyhow::Error::new)
50}
51/// Creates the parser settings struct from the global configuration values as
52/// wel as the capabilities  struct.
53pub fn settings_from_capabilities(cap: &Capabilities) -> ParserSettings {
54	ParserSettings {
55		object_recursion_limit: MAX_OBJECT_PARSING_DEPTH as usize,
56		query_recursion_limit: MAX_QUERY_PARSING_DEPTH as usize,
57		files_enabled: cap.allows_experimental(&ExperimentalTarget::Files),
58		surrealism_enabled: cap.allows_experimental(&ExperimentalTarget::Surrealism),
59		..Default::default()
60	}
61}
62/// Parses a SurrealQL query.
63///
64/// During query parsing, the total depth of calls to parse values (including
65/// arrays, expressions, functions, objects, sub-queries), Javascript values,
66/// and geometry collections count against a computation depth limit. If the
67/// limit is reached, parsing will return an error,
68/// as opposed to spending more time and potentially overflowing the call stack.
69///
70/// If you encounter this limit and believe that it should be increased,
71/// please [open an issue](https://github.com/surrealdb/surrealdb/issues)!
72pub fn parse(input: &str) -> Result<Ast> {
73	let capabilities = Capabilities::all();
74	parse_with_capabilities(input, &capabilities)
75}
76/// Parses a SurrealQL query.
77///
78/// During query parsing, the total depth of calls to parse values (including
79/// arrays, expressions, functions, objects, sub-queries), Javascript values,
80/// and geometry collections count against a computation depth limit. If the
81/// limit is reached, parsing will return an error,
82/// as opposed to spending more time and potentially overflowing the call stack.
83///
84/// If you encounter this limit and believe that it should be increased,
85/// please [open an issue](https://github.com/surrealdb/surrealdb/issues)!
86pub fn parse_with_capabilities(input: &str, capabilities: &Capabilities) -> Result<Ast> {
87	trace!(target : TARGET, "Parsing SurrealQL query");
88	parse_with_settings(
89		input.as_bytes(),
90		settings_from_capabilities(capabilities),
91		async |parser, stk| parser.parse_query(stk).await,
92	)
93}
94/// Parses a SurrealQL [`Expr`].
95#[allow(dead_code)]
96pub fn expr(input: &str) -> Result<Expr> {
97	let capabilities = Capabilities::all();
98	expr_with_capabilities(input, &capabilities)
99}
100/// Parses a SurrealQL [`Value`].
101#[allow(dead_code)]
102pub fn expr_with_capabilities(input: &str, capabilities: &Capabilities) -> Result<Expr> {
103	trace!(target : TARGET, "Parsing SurrealQL value");
104	parse_with_settings(
105		input.as_bytes(),
106		settings_from_capabilities(capabilities),
107		async |parser, stk| parser.parse_expr_field(stk).await,
108	)
109}
110/// Parses a SurrealQL function name.
111pub fn function(input: &str) -> Result<Function> {
112	let capabilities = Capabilities::all();
113	function_with_capabilities(input, &capabilities)
114}
115/// Parses a SurrealQL function name.
116pub fn function_with_capabilities(input: &str, capabilities: &Capabilities) -> Result<Function> {
117	trace!(target : TARGET, "Parsing SurrealQL function name");
118	parse_with_settings(
119		input.as_bytes(),
120		settings_from_capabilities(capabilities),
121		async |parser, _stk| parser.parse_function_name().await,
122	)
123}
124/// Parses JSON into an inert SurrealQL [`PublicValue`].
125pub fn json(input: &str) -> Result<PublicValue> {
126	trace!(target : TARGET, "Parsing inert JSON value");
127	parse_with(input.as_bytes(), async |parser, stk| {
128		parser.parse_json(stk).await
129	})
130}
131/// Parses a SurrealQL [`Idiom`]
132pub fn idiom(input: &str) -> Result<Idiom> {
133	trace!(target : TARGET, "Parsing SurrealQL idiom");
134	parse_with(input.as_bytes(), async |parser, stk| {
135		parser.parse_plain_idiom(stk).await
136	})
137}
138/// Parse a datetime without enclosing delimiters from a string.
139pub fn datetime(input: &str) -> Result<PublicDatetime> {
140	trace!(target : TARGET, "Parsing SurrealQL datetime");
141	ensure!(input.len() <= u32::MAX as usize, Error::QueryTooLarge);
142	match Lexer::lex_datetime(input) {
143		Ok(x) => Ok(x),
144		Err(e) => bail!(Error::InvalidQuery(e.render_on(input))),
145	}
146}
147/// Parse a duration from a string.
148pub fn duration(input: &str) -> Result<PublicDuration> {
149	trace!(target : TARGET, "Parsing SurrealQL duration");
150	ensure!(input.len() <= u32::MAX as usize, Error::QueryTooLarge);
151	let mut parser = Parser::new(input.as_bytes());
152	parser
153		.next_token_value::<PublicDuration>()
154		.and_then(|e| parser.assert_finished().map(|_| e))
155		.map_err(|e| e.render_on(input))
156		.map_err(Error::InvalidQuery)
157		.map_err(anyhow::Error::new)
158}
159/// Parse a record id.
160pub fn record_id(input: &str) -> Result<PublicRecordId> {
161	trace!(target : TARGET, "Parsing SurrealQL record id");
162	parse_with(input.as_bytes(), async |parser, stk| {
163		parser.parse_value_record_id(stk).await
164	})
165}
166/// Parse a table name from a string.
167pub fn table(input: &str) -> Result<crate::compat::val::TableName> {
168	trace!(target : TARGET, "Parsing SurrealQL table name");
169	parse_with(input.as_bytes(), async |parser, _stk| {
170		let ident = parser.parse_ident()?;
171		Ok(crate::compat::val::TableName::new(ident))
172	})
173}
174/// Parse a block, expects the value to be wrapped in `{}`.
175pub fn block(input: &str) -> Result<Block> {
176	trace!(target : TARGET, "Parsing SurrealQL block");
177	parse_with_settings(
178		input.as_bytes(),
179		ParserSettings {
180			legacy_strands: false,
181			flexible_record_id: true,
182			files_enabled: true,
183			surrealism_enabled: true,
184			..Default::default()
185		},
186		async |parser, stk| {
187			let token = parser.peek();
188			match token.kind {
189				t!("{") => {
190					let start = parser.pop_peek().span;
191					parser.parse_block(stk, start).await
192				}
193				found => Err(error::SyntaxError::new(format_args!(
194					"Unexpected token `{found}` expected `{{`"
195				))
196				.with_span(token.span, error::MessageKind::Error)),
197			}
198		},
199	)
200}
201/// Parses a SurrealQL [`Value`] and parses values within strings.
202#[allow(dead_code)]
203pub fn expr_legacy_strand(input: &str) -> Result<Expr> {
204	trace!(target : TARGET, "Parsing SurrealQL value, with legacy strings");
205	let settings = ParserSettings {
206		object_recursion_limit: MAX_OBJECT_PARSING_DEPTH as usize,
207		query_recursion_limit: MAX_QUERY_PARSING_DEPTH as usize,
208		legacy_strands: true,
209		..Default::default()
210	};
211	parse_with_settings(input.as_bytes(), settings, async |parser, stk| {
212		parser.parse_expr_field(stk).await
213	})
214}
215/// Parses a SurrealQL [`PublicValue`] and parses values within strings.
216pub fn value(input: &str) -> Result<PublicValue> {
217	trace!(target : TARGET, "Parsing SurrealQL value, with legacy strings");
218	let settings = ParserSettings {
219		object_recursion_limit: MAX_OBJECT_PARSING_DEPTH as usize,
220		query_recursion_limit: MAX_QUERY_PARSING_DEPTH as usize,
221		..Default::default()
222	};
223	parse_with_settings(input.as_bytes(), settings, async |parser, stk| {
224		parser.parse_value(stk).await
225	})
226}
227/// Parses a SurrealQL [`PublicValue`] and parses values within strings.
228pub fn value_legacy_strand(input: &str) -> Result<PublicValue> {
229	trace!(target : TARGET, "Parsing SurrealQL value, with legacy strings");
230	let settings = ParserSettings {
231		object_recursion_limit: MAX_OBJECT_PARSING_DEPTH as usize,
232		query_recursion_limit: MAX_QUERY_PARSING_DEPTH as usize,
233		legacy_strands: true,
234		..Default::default()
235	};
236	parse_with_settings(input.as_bytes(), settings, async |parser, stk| {
237		parser.parse_value(stk).await
238	})
239}
240/// Parses JSON into an inert SurrealQL [`PublicValue`] and parses values within
241/// strings.
242pub fn json_legacy_strand(input: &str) -> Result<PublicValue> {
243	trace!(target : TARGET, "Parsing inert JSON value, with legacy strings");
244	let settings = ParserSettings {
245		object_recursion_limit: MAX_OBJECT_PARSING_DEPTH as usize,
246		query_recursion_limit: MAX_QUERY_PARSING_DEPTH as usize,
247		legacy_strands: true,
248		..Default::default()
249	};
250	parse_with_settings(input.as_bytes(), settings, async |parser, stk| {
251		parser.parse_json(stk).await
252	})
253}
254/// Parse a kind from a string.
255pub fn kind(input: &str) -> Result<Kind> {
256	trace!(target : TARGET, "Parsing SurrealQL duration");
257	parse_with(input.as_bytes(), async |parser, stk| {
258		parser.parse_inner_kind(stk).await
259	})
260}
261/// Extracts the tables from the given kind definition string.
262///
263/// Note: This is only used by surrealql.wasm for use in Surrealist.
264///
265/// # Examples
266///
267/// ```ignore
268/// let tables = extract_tables_from_kind("record<users | posts>");
269/// assert_eq!(tables, vec!["posts", "users"]);
270/// ```ignore
271#[doc(hidden)]
272pub fn extract_tables_from_kind(sql: &str) -> Result<Vec<String>> {
273	let kind = kind(sql)?;
274	let mut found_tables = HashSet::new();
275	extract_tables_from_kind_impl(&kind, &mut found_tables);
276	let mut tables_sorted: Vec<String> = found_tables.into_iter().collect();
277	tables_sorted.sort();
278	Ok(tables_sorted)
279}
280fn extract_tables_from_kind_impl(kind: &Kind, tables: &mut HashSet<String>) {
281	match kind {
282		Kind::Any
283		| Kind::None
284		| Kind::Null
285		| Kind::Bool
286		| Kind::Bytes
287		| Kind::Datetime
288		| Kind::Decimal
289		| Kind::Duration
290		| Kind::Float
291		| Kind::Int
292		| Kind::Number
293		| Kind::Object
294		| Kind::String
295		| Kind::Uuid
296		| Kind::Regex
297		| Kind::Geometry(_) => {}
298		Kind::Table(ts) => {
299			for table in ts {
300				tables.insert(table.clone());
301			}
302		}
303		Kind::Record(ts) => {
304			for table in ts {
305				tables.insert(table.clone());
306			}
307		}
308		Kind::Either(kinds) => {
309			for kind in kinds {
310				extract_tables_from_kind_impl(kind, tables);
311			}
312		}
313		Kind::Set(kind, _) => {
314			extract_tables_from_kind_impl(kind, tables);
315		}
316		Kind::Array(kind, _) => {
317			extract_tables_from_kind_impl(kind, tables);
318		}
319		Kind::Function(_, _) => {}
320		Kind::Range => {}
321		Kind::Literal(literal) => match literal {
322			KindLiteral::Array(kinds) => {
323				for kind in kinds {
324					extract_tables_from_kind_impl(kind, tables);
325				}
326			}
327			KindLiteral::Object(kinds) => {
328				for kind in kinds.values() {
329					extract_tables_from_kind_impl(kind, tables);
330				}
331			}
332			_ => {}
333		},
334		Kind::File(_) => {}
335	}
336}