surrealdb_core/syn/parser/
object.rs

1use std::collections::BTreeMap;
2
3use reblessive::Stk;
4
5use crate::{
6	sql::{Block, Geometry, Object, Strand, Value},
7	syn::{
8		error::bail,
9		lexer::compound,
10		parser::{enter_object_recursion, mac::expected, ParseResult, Parser},
11		token::{t, Glued, Span, TokenKind},
12	},
13};
14
15use super::mac::unexpected;
16
17impl Parser<'_> {
18	/// Parse an production which starts with an `{`
19	///
20	/// Either a block statemnt, a object or geometry.
21	pub(super) async fn parse_object_like(
22		&mut self,
23		ctx: &mut Stk,
24		start: Span,
25	) -> ParseResult<Value> {
26		if self.eat(t!("}")) {
27			// empty object, just return
28			enter_object_recursion!(_this = self => {
29				return Ok(Value::Object(Object::default()));
30			})
31		}
32
33		// Now check first if it can be an object.
34		if self.glue_and_peek1()?.kind == t!(":") {
35			enter_object_recursion!(this = self => {
36			   return this.parse_object_or_geometry(ctx, start).await;
37			})
38		}
39
40		// not an object so instead parse as a block.
41		self.parse_block(ctx, start).await.map(Box::new).map(Value::Block)
42	}
43
44	async fn parse_object_or_geometry_after_type(
45		&mut self,
46		ctx: &mut Stk,
47		start: Span,
48		key: String,
49	) -> ParseResult<Value> {
50		expected!(self, t!(":"));
51		// for it to be geometry the next value must be a strand like.
52		let (t!("\"") | t!("'") | TokenKind::Glued(Glued::Strand)) = self.peek_kind() else {
53			return self
54				.parse_object_from_key(ctx, key, BTreeMap::new(), start)
55				.await
56				.map(Value::Object);
57		};
58
59		// We know it is a strand so check if the type is one of the allowe geometry types.
60		// If it is, there are some which all take roughly the save type of value and produce a
61		// similar output, which is parsed with parse_geometry_after_type
62		//
63		// GeometryCollection however has a different object key for its value, so it is handled
64		// appart from the others.
65		let type_value = self.next_token_value::<Strand>()?.0;
66		match type_value.as_str() {
67			"Point" => {
68				// we matched a type correctly but the field containing the geometry value
69				// can still be wrong.
70				//
71				// we can unwrap strand since we just matched it to not be an err.
72				self.parse_geometry_after_type(
73					ctx,
74					start,
75					key,
76					type_value,
77					Geometry::array_to_point,
78					|x| Value::Geometry(Geometry::Point(x)),
79				)
80				.await
81			}
82			"LineString" => {
83				self.parse_geometry_after_type(
84					ctx,
85					start,
86					key,
87					type_value,
88					Geometry::array_to_line,
89					|x| Value::Geometry(Geometry::Line(x)),
90				)
91				.await
92			}
93			"Polygon" => {
94				self.parse_geometry_after_type(
95					ctx,
96					start,
97					key,
98					type_value,
99					Geometry::array_to_polygon,
100					|x| Value::Geometry(Geometry::Polygon(x)),
101				)
102				.await
103			}
104			"MultiPoint" => {
105				self.parse_geometry_after_type(
106					ctx,
107					start,
108					key,
109					type_value,
110					Geometry::array_to_multipoint,
111					|x| Value::Geometry(Geometry::MultiPoint(x)),
112				)
113				.await
114			}
115			"MultiLineString" => {
116				self.parse_geometry_after_type(
117					ctx,
118					start,
119					key,
120					type_value,
121					Geometry::array_to_multiline,
122					|x| Value::Geometry(Geometry::MultiLine(x)),
123				)
124				.await
125			}
126			"MultiPolygon" => {
127				self.parse_geometry_after_type(
128					ctx,
129					start,
130					key,
131					type_value,
132					Geometry::array_to_multipolygon,
133					|x| Value::Geometry(Geometry::MultiPolygon(x)),
134				)
135				.await
136			}
137			"GeometryCollection" => {
138				if !self.eat(t!(",")) {
139					// missing next field, not a geometry.
140					return self
141						.parse_object_from_map(
142							ctx,
143							BTreeMap::from([(key, Value::Strand(type_value.into()))]),
144							start,
145						)
146						.await
147						.map(Value::Object);
148				}
149
150				let coord_key = self.parse_object_key()?;
151				if coord_key != "geometries" {
152					expected!(self, t!(":"));
153					// invalid field key, not a Geometry
154					return self
155						.parse_object_from_key(
156							ctx,
157							coord_key,
158							BTreeMap::from([(key, Value::Strand(type_value.into()))]),
159							start,
160						)
161						.await
162						.map(Value::Object);
163				}
164
165				expected!(self, t!(":"));
166
167				let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
168
169				// check for an object end, if it doesn't end it is not a geometry.
170				if !self.eat(t!(",")) {
171					self.expect_closing_delimiter(t!("}"), start)?;
172				} else {
173					if self.peek_kind() != t!("}") {
174						// A comma and then no brace. more then two fields, not a geometry.
175						return self
176							.parse_object_from_map(
177								ctx,
178								BTreeMap::from([
179									(key, Value::Strand(type_value.into())),
180									(coord_key, value),
181								]),
182								start,
183							)
184							.await
185							.map(Value::Object);
186					}
187					self.pop_peek();
188				}
189
190				// try to convert to the right value.
191				if let Value::Array(x) = value {
192					// test first to avoid a cloning.
193					if x.iter().all(|x| matches!(x, Value::Geometry(_))) {
194						let geometries =
195							x.0.into_iter()
196								.map(|x| {
197									if let Value::Geometry(x) = x {
198										x
199									} else {
200										unreachable!()
201									}
202								})
203								.collect();
204
205						return Ok(Value::Geometry(Geometry::Collection(geometries)));
206					}
207
208					return Ok(Value::Object(Object(BTreeMap::from([
209						(key, Value::Strand(type_value.into())),
210						(coord_key, Value::Array(x)),
211					]))));
212				}
213
214				// Couldn't convert so it is a normal object.
215				Ok(Value::Object(Object(BTreeMap::from([
216					(key, Value::Strand(type_value.into())),
217					(coord_key, value),
218				]))))
219			}
220			// key was not one of the allowed keys so it is a normal object.
221			_ => {
222				let object = BTreeMap::from([(key, Value::Strand(type_value.into()))]);
223
224				if self.eat(t!(",")) {
225					self.parse_object_from_map(ctx, object, start).await.map(Value::Object)
226				} else {
227					self.expect_closing_delimiter(t!("}"), start)?;
228					Ok(Value::Object(Object(object)))
229				}
230			}
231		}
232	}
233
234	async fn parse_object_or_geometry_after_coordinates(
235		&mut self,
236		ctx: &mut Stk,
237		start: Span,
238		key: String,
239	) -> ParseResult<Value> {
240		expected!(self, t!(":"));
241
242		// found coordinates field, next must be a coordinates value but we don't know
243		// which until we match type.
244		let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
245
246		if !self.eat(t!(",")) {
247			// no comma object must end early.
248			self.expect_closing_delimiter(t!("}"), start)?;
249			return Ok(Value::Object(Object(BTreeMap::from([(key, value)]))));
250		}
251
252		if self.eat(t!("}")) {
253			// object ends early.
254			return Ok(Value::Object(Object(BTreeMap::from([(key, value)]))));
255		}
256
257		let type_key = self.parse_object_key()?;
258		if type_key != "type" {
259			expected!(self, t!(":"));
260			// not the right field, return object.
261			return self
262				.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
263				.await
264				.map(Value::Object);
265		}
266		expected!(self, t!(":"));
267
268		let (t!("\"") | t!("'")) = self.peek_kind() else {
269			// not the right value also move back to parsing an object.
270			return self
271				.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
272				.await
273				.map(Value::Object);
274		};
275
276		let type_value = self.next_token_value::<Strand>()?.0;
277		let ate_comma = self.eat(t!(","));
278		// match the type and then match the coordinates field to a value of that type.
279		match type_value.as_str() {
280			"Point" => {
281				if self.eat(t!("}")) {
282					if let Some(point) = Geometry::array_to_point(&value) {
283						return Ok(Value::Geometry(Geometry::Point(point)));
284					}
285				}
286			}
287			"LineString" => {
288				if self.eat(t!("}")) {
289					if let Some(point) = Geometry::array_to_line(&value) {
290						return Ok(Value::Geometry(Geometry::Line(point)));
291					}
292				}
293			}
294			"Polygon" => {
295				if self.eat(t!("}")) {
296					if let Some(point) = Geometry::array_to_polygon(&value) {
297						return Ok(Value::Geometry(Geometry::Polygon(point)));
298					}
299				}
300			}
301			"MultiPoint" => {
302				if self.eat(t!("}")) {
303					if let Some(point) = Geometry::array_to_multipolygon(&value) {
304						return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
305					}
306				}
307			}
308			"MultiLineString" => {
309				if self.eat(t!("}")) {
310					if let Some(point) = Geometry::array_to_multiline(&value) {
311						return Ok(Value::Geometry(Geometry::MultiLine(point)));
312					}
313				}
314			}
315			"MultiPolygon" => {
316				if self.eat(t!("}")) {
317					if let Some(point) = Geometry::array_to_multipolygon(&value) {
318						return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
319					}
320				}
321			}
322			_ => {}
323		};
324
325		// type field or coordinates value didn't match or the object continues after to
326		// fields.
327
328		if !ate_comma {
329			self.expect_closing_delimiter(t!("}"), start)?;
330			return Ok(Value::Object(Object(BTreeMap::from([
331				(key, value),
332				(type_key, Value::Strand(type_value.into())),
333			]))));
334		}
335
336		self.parse_object_from_map(
337			ctx,
338			BTreeMap::from([(key, value), (type_key, Value::Strand(type_value.into()))]),
339			start,
340		)
341		.await
342		.map(Value::Object)
343	}
344
345	async fn parse_object_or_geometry_after_geometries(
346		&mut self,
347		ctx: &mut Stk,
348		start: Span,
349		key: String,
350	) -> ParseResult<Value> {
351		// 'geometries' key can only happen in a GeometryCollection, so try to parse that.
352		expected!(self, t!(":"));
353
354		let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
355
356		// if the object ends here, it is not a geometry.
357		if !self.eat(t!(",")) || self.peek_kind() == t!("}") {
358			self.expect_closing_delimiter(t!("}"), start)?;
359			return Ok(Value::Object(Object(BTreeMap::from([(key, value)]))));
360		}
361
362		// parse the next objectkey
363		let type_key = self.parse_object_key()?;
364		// it if isn't 'type' this object is not a geometry, so bail.
365		if type_key != "type" {
366			expected!(self, t!(":"));
367			return self
368				.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
369				.await
370				.map(Value::Object);
371		}
372		expected!(self, t!(":"));
373		// check if the next key is a strand.
374		let (t!("\"") | t!("'")) = self.peek_kind() else {
375			// not the right value also move back to parsing an object.
376			return self
377				.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
378				.await
379				.map(Value::Object);
380		};
381
382		let type_value = self.next_token_value::<Strand>()?.0;
383		let ate_comma = self.eat(t!(","));
384
385		if type_value == "GeometryCollection" && self.eat(t!("}")) {
386			if let Value::Array(ref x) = value {
387				if x.iter().all(|x| matches!(x, Value::Geometry(_))) {
388					let Value::Array(x) = value else {
389						unreachable!()
390					};
391					let geometries = x
392						.into_iter()
393						.map(|x| {
394							if let Value::Geometry(x) = x {
395								x
396							} else {
397								unreachable!()
398							}
399						})
400						.collect();
401					return Ok(Value::Geometry(Geometry::Collection(geometries)));
402				}
403			}
404		}
405
406		// Either type value didn't match or gemoetry value didn't match.
407		// Regardless the current object is not a geometry.
408
409		if !ate_comma {
410			self.expect_closing_delimiter(t!("}"), start)?;
411			return Ok(Value::Object(Object(BTreeMap::from([
412				(key, value),
413				(type_key, Value::Strand(type_value.into())),
414			]))));
415		}
416
417		self.parse_object_from_map(
418			ctx,
419			BTreeMap::from([(key, value), (type_key, Value::Strand(type_value.into()))]),
420			start,
421		)
422		.await
423		.map(Value::Object)
424	}
425
426	/// Parse a production starting with an `{` as either an object or a geometry.
427	///
428	/// This function tries to match an object to an geometry like object and if it is unable
429	/// fallsback to parsing normal objects.
430	async fn parse_object_or_geometry(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Value> {
431		// empty object was already matched previously so next must be a key.
432		let key = self.parse_object_key()?;
433		// the order of fields of a geometry does not matter so check if it is any of geometry like keys
434		// "type" : could be the type of the object.
435		// "collections": could be a geometry collection.
436		// "geometry": could be the values of geometry.
437		match key.as_str() {
438			"type" => self.parse_object_or_geometry_after_type(ctx, start, key).await,
439			"coordinates" => self.parse_object_or_geometry_after_coordinates(ctx, start, key).await,
440			"geometries" => self.parse_object_or_geometry_after_geometries(ctx, start, key).await,
441			_ => {
442				expected!(self, t!(":"));
443				self.parse_object_from_key(ctx, key, BTreeMap::new(), start)
444					.await
445					.map(Value::Object)
446			}
447		}
448	}
449
450	async fn parse_geometry_after_type<F, Fm, R>(
451		&mut self,
452		ctx: &mut Stk,
453		start: Span,
454		key: String,
455		strand: String,
456		capture: F,
457		map: Fm,
458	) -> ParseResult<Value>
459	where
460		F: FnOnce(&Value) -> Option<R>,
461		Fm: FnOnce(R) -> Value,
462	{
463		if !self.eat(t!(",")) {
464			// there is not second field. not a geometry
465			self.expect_closing_delimiter(t!("}"), start)?;
466			return Ok(Value::Object(Object(BTreeMap::from([(
467				key,
468				Value::Strand(strand.into()),
469			)]))));
470		}
471		let coord_key = self.parse_object_key()?;
472		if coord_key != "coordinates" {
473			expected!(self, t!(":"));
474			// next field was not correct, fallback to parsing plain object.
475			return self
476				.parse_object_from_key(
477					ctx,
478					coord_key,
479					BTreeMap::from([(key, Value::Strand(strand.into()))]),
480					start,
481				)
482				.await
483				.map(Value::Object);
484		}
485		expected!(self, t!(":"));
486		let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
487		let comma = self.eat(t!(","));
488		if !self.eat(t!("}")) {
489			// the object didn't end, either an error or not a geometry.
490			if !comma {
491				bail!("Unexpected token, expected delimiter `}}`",
492					@self.recent_span(),
493					@start => "expected this delimiter to close"
494				);
495			}
496
497			return self
498				.parse_object_from_map(
499					ctx,
500					BTreeMap::from([(key, Value::Strand(strand.into())), (coord_key, value)]),
501					start,
502				)
503				.await
504				.map(Value::Object);
505		}
506
507		let Some(v) = capture(&value) else {
508			// failed to match the geometry value, just a plain object.
509			return Ok(Value::Object(Object(BTreeMap::from([
510				(key, Value::Strand(strand.into())),
511				(coord_key, value),
512			]))));
513		};
514		// successfully matched the value, it is a geometry.
515		Ok(map(v))
516	}
517
518	async fn parse_object_from_key(
519		&mut self,
520		ctx: &mut Stk,
521		key: String,
522		mut map: BTreeMap<String, Value>,
523		start: Span,
524	) -> ParseResult<Object> {
525		let v = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
526		map.insert(key, v);
527		if !self.eat(t!(",")) {
528			self.expect_closing_delimiter(t!("}"), start)?;
529			return Ok(Object(map));
530		}
531		self.parse_object_from_map(ctx, map, start).await
532	}
533
534	/// Parses an object.
535	///
536	/// Expects the span of the starting `{` as an argument.
537	///
538	/// # Parser state
539	/// Expects the first `{` to already have been eaten.
540	pub(super) async fn parse_object(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Object> {
541		enter_object_recursion!(this = self => {
542			this.parse_object_from_map(ctx, BTreeMap::new(), start).await
543		})
544	}
545
546	async fn parse_object_from_map(
547		&mut self,
548		ctx: &mut Stk,
549		mut map: BTreeMap<String, Value>,
550		start: Span,
551	) -> ParseResult<Object> {
552		loop {
553			if self.eat(t!("}")) {
554				return Ok(Object(map));
555			}
556
557			let (key, value) = self.parse_object_entry(ctx).await?;
558			// TODO: Error on duplicate key?
559			map.insert(key, value);
560
561			if !self.eat(t!(",")) {
562				self.expect_closing_delimiter(t!("}"), start)?;
563				return Ok(Object(map));
564			}
565		}
566	}
567
568	/// Parses a block of statements
569	///
570	/// # Parser State
571	/// Expects the starting `{` to have already been eaten and its span to be handed to this
572	/// functions as the `start` parameter.
573	pub async fn parse_block(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Block> {
574		let mut statements = Vec::new();
575		loop {
576			while self.eat(t!(";")) {}
577			if self.eat(t!("}")) {
578				break;
579			}
580
581			let stmt = ctx.run(|ctx| self.parse_entry(ctx)).await?;
582			statements.push(stmt);
583			if !self.eat(t!(";")) {
584				self.expect_closing_delimiter(t!("}"), start)?;
585				break;
586			}
587		}
588		Ok(Block(statements))
589	}
590
591	/// Parse a single entry in the object, i.e. `field: value + 1` in the object `{ field: value +
592	/// 1 }`
593	async fn parse_object_entry(&mut self, ctx: &mut Stk) -> ParseResult<(String, Value)> {
594		let text = self.parse_object_key()?;
595		expected!(self, t!(":"));
596		let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
597		Ok((text, value))
598	}
599
600	/// Parses the key of an object, i.e. `field` in the object `{ field: 1 }`.
601	pub(super) fn parse_object_key(&mut self) -> ParseResult<String> {
602		let token = self.peek();
603		match token.kind {
604			x if Self::kind_is_keyword_like(x) => {
605				self.pop_peek();
606				let str = self.lexer.span_str(token.span);
607				Ok(str.to_string())
608			}
609			TokenKind::Identifier => {
610				self.pop_peek();
611				let str = self.lexer.string.take().unwrap();
612				Ok(str)
613			}
614			t!("\"") | t!("'") | TokenKind::Glued(Glued::Strand) => {
615				let str = self.next_token_value::<Strand>()?.0;
616				Ok(str)
617			}
618			TokenKind::Digits => {
619				self.pop_peek();
620				let span = self.lexer.lex_compound(token, compound::number)?.span;
621				let str = self.lexer.span_str(span);
622				Ok(str.to_string())
623			}
624			TokenKind::Glued(Glued::Number) => {
625				self.pop_peek();
626				let str = self.lexer.span_str(token.span);
627				Ok(str.to_string())
628			}
629			_ => unexpected!(self, token, "an object key"),
630		}
631	}
632}
633
634#[cfg(test)]
635mod test {
636	use super::*;
637	use crate::syn::Parse;
638
639	#[test]
640	fn block_value() {
641		let sql = "{ 80 }";
642		let out = Value::parse(sql);
643		assert_eq!(sql, out.to_string())
644	}
645
646	#[test]
647	fn block_ifelse() {
648		let sql = "{ RETURN IF true THEN 50 ELSE 40 END; }";
649		let out = Value::parse(sql);
650		assert_eq!(sql, out.to_string())
651	}
652
653	#[test]
654	fn block_multiple() {
655		let sql = r#"{
656
657	LET $person = (SELECT * FROM person WHERE first = $first AND last = $last AND birthday = $birthday);
658
659	RETURN IF $person[0].id THEN
660		$person[0]
661	ELSE
662		(CREATE person SET first = $first, last = $last, birthday = $birthday)
663	END;
664
665}"#;
666		let out = Value::parse(sql);
667		assert_eq!(sql, format!("{:#}", out))
668	}
669}
670
671#[cfg(test)]
672mod tests {
673	use super::*;
674	use crate::syn::Parse;
675
676	#[test]
677	fn simple() {
678		let sql = "(-0.118092, 51.509865)";
679		let out = Value::parse(sql);
680		assert!(matches!(out, Value::Geometry(_)));
681		assert_eq!("(-0.118092, 51.509865)", format!("{}", out));
682	}
683
684	#[test]
685	fn point() {
686		let sql = r#"{
687			type: 'Point',
688			coordinates: [-0.118092, 51.509865]
689		}"#;
690		let out = Value::parse(sql);
691		assert!(matches!(out, Value::Geometry(_)));
692		assert_eq!("(-0.118092, 51.509865)", format!("{}", out));
693	}
694
695	#[test]
696	fn polygon_exterior() {
697		let sql = r#"{
698			type: 'Polygon',
699			coordinates: [
700				[
701					[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
702					[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
703					[-0.38314819, 51.37692386]
704				]
705			]
706		}"#;
707		let out = Value::parse(sql);
708		assert!(matches!(out, Value::Geometry(_)));
709		assert_eq!("{ type: 'Polygon', coordinates: [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]]] }", format!("{}", out));
710	}
711
712	#[test]
713	fn polygon_interior() {
714		let sql = r#"{
715			type: 'Polygon',
716			coordinates: [
717				[
718					[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
719					[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
720					[-0.38314819, 51.37692386]
721				],
722				[
723					[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
724					[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
725					[-0.38314819, 51.37692386]
726				]
727			]
728		}"#;
729		let out = Value::parse(sql);
730		assert!(matches!(out, Value::Geometry(_)));
731		assert_eq!("{ type: 'Polygon', coordinates: [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]], [[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]]] }", format!("{}", out));
732	}
733}