surrealdb-core 3.2.0

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
use reblessive::Stack;
use rust_decimal::Decimal;
use surrealdb_strand::Strand;
use surrealdb_types::ToSql;

use crate::sql::literal::ObjectEntry;
use crate::sql::{
	BinaryOperator, Constant, Expr, Idiom, Literal, Part, RecordIdKeyLit, RecordIdLit,
};
use crate::syn;
use crate::syn::parser::{Parser, ParserSettings};
use crate::types::PublicGeometry;

#[test]
fn parse_index_expression() {
	let value = syn::parse_with("a[1 + 1]".as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	let Expr::Idiom(x) = value else {
		panic!("not the right value type");
	};
	assert_eq!(x.0[0], Part::Field(Strand::new_static("a")));
	assert_eq!(
		x.0[1],
		Part::Value(Expr::Binary {
			left: Box::new(Expr::Literal(Literal::Integer(1))),
			op: BinaryOperator::Add,
			right: Box::new(Expr::Literal(Literal::Integer(1))),
		})
	)
}

#[test]
fn parse_coordinate() {
	let coord = syn::parse_with("(1.88, -18.0)".as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	let Expr::Literal(Literal::Geometry(PublicGeometry::Point(x))) = coord else {
		panic!("not the right value");
	};
	assert_eq!(x.x(), 1.88);
	assert_eq!(x.y(), -18.0);
}

#[test]
fn parse_numeric_object_key() {
	let v = syn::parse_with("{ 00: 0 }".as_bytes(), async |parser, stk| {
		parser.parse_expr_table(stk).await
	})
	.unwrap();
	let Expr::Literal(Literal::Object(object)) = v else {
		panic!("not an object");
	};
	assert!(object.len() == 1);
	assert_eq!(object[0].value, Expr::Literal(Literal::Integer(0)));
}

#[test]
fn parse_range_operator() {
	syn::parse_with("1..2".as_bytes(), async |parser, stk| parser.parse_expr_field(stk).await)
		.unwrap();
}

#[test]
fn parse_large_depth_object() {
	let mut text = String::new();
	let start = r#" { foo: "#;
	let middle = r#" {bar: 1} "#;
	let end = r#" } "#;

	for _ in 0..1000 {
		text.push_str(start);
	}
	text.push_str(middle);
	for _ in 0..1000 {
		text.push_str(end);
	}
	let mut parser = Parser::new_with_settings(
		text.as_bytes(),
		ParserSettings {
			query_recursion_limit: 100000,
			object_recursion_limit: 100000,
			expr_recursion_limit: 100000,
			..Default::default()
		},
	);
	let mut stack = Stack::new();
	let query = stack.enter(|stk| parser.parse_expr_inherit(stk)).finish().unwrap();
	let Expr::Literal(Literal::Object(ref object)) = query else {
		panic!()
	};
	let mut object = object;
	for _ in 0..999 {
		let Some(Expr::Literal(Literal::Object(new_object))) =
			object.iter().find(|x| x.key == "foo").map(|x| &x.value)
		else {
			panic!()
		};
		object = new_object
	}
}

#[test]
fn parse_large_depth_record_id() {
	let mut text = String::new();
	let start = r#" a:[ "#;
	let middle = r#" b:{c: 1} "#;
	let end = r#" ] "#;

	for _ in 0..1000 {
		text.push_str(start);
	}
	text.push_str(middle);
	for _ in 0..1000 {
		text.push_str(end);
	}
	let mut parser = Parser::new_with_settings(
		text.as_bytes(),
		ParserSettings {
			query_recursion_limit: 100000,
			object_recursion_limit: 100000,
			expr_recursion_limit: 100000,
			..Default::default()
		},
	);
	let mut stack = Stack::new();
	let query = stack.enter(|stk| parser.parse_expr_inherit(stk)).finish().unwrap();
	let Expr::Literal(Literal::RecordId(ref rid)) = query else {
		panic!()
	};
	let mut rid = rid;
	for _ in 0..999 {
		let RecordIdKeyLit::Array(ref x) = rid.key else {
			panic!()
		};
		let Expr::Literal(Literal::RecordId(ref new_rid)) = x[0] else {
			panic!()
		};
		rid = new_rid
	}
}

#[test]
fn parse_recursive_record_string() {
	let res =
		syn::parse_with(r#" r"a:[r\"b:{c: r\\\"d:1\\\"}\"]" "#.as_bytes(), async |parser, stk| {
			parser.parse_expr_field(stk).await
		})
		.unwrap();
	assert_eq!(
		res,
		Expr::Literal(Literal::RecordId(RecordIdLit {
			table: "a".into(),
			key: RecordIdKeyLit::Array(vec![Expr::Literal(Literal::RecordId(RecordIdLit {
				table: "b".into(),
				key: RecordIdKeyLit::Object(vec![ObjectEntry {
					key: "c".into(),
					value: Expr::Literal(Literal::RecordId(RecordIdLit {
						table: "d".into(),
						key: RecordIdKeyLit::Number(1)
					}))
				}])
			}))])
		}))
	)
}

#[test]
fn parse_record_string_2() {
	let res = syn::parse_with(r#" r'a:["foo"]' "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(
		res,
		Expr::Literal(Literal::RecordId(RecordIdLit {
			table: "a".into(),
			key: RecordIdKeyLit::Array(vec![Expr::Literal(Literal::String(Strand::new_static(
				"foo"
			)))])
		}))
	)
}

#[test]
fn parse_i64() {
	let res = syn::parse_with(r#" -9223372036854775808 "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(res, Expr::Literal(Literal::Integer(i64::MIN)));

	let res = syn::parse_with(r#" 9223372036854775807 "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(res, Expr::Literal(Literal::Integer(i64::MAX)));
}

#[test]
fn parse_decimal() {
	let res = syn::parse_with(r#" 0dec "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(res, Expr::Literal(Literal::Decimal(Decimal::ZERO)));
}

#[test]
fn constant_lowercase() {
	let out = syn::parse_with(r#" math::pi "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathPi));

	let out = syn::parse_with(r#" math::inf "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathInfinity));

	let out = syn::parse_with(r#" math::neg_inf "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathNegInfinity));

	let out = syn::parse_with(r#" time::epoch "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::TimeEpoch));
}

#[test]
fn constant_uppercase() {
	let out = syn::parse_with(r#" MATH::PI "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathPi));

	let out = syn::parse_with(r#" MATH::INFINITY "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathInfinity));

	let out = syn::parse_with(r#" MATH::NEG_INFINITY "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathNegInfinity));

	let out = syn::parse_with(r#" TIME::EPOCH "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::TimeEpoch));
}

#[test]
fn constant_mixedcase() {
	let out = syn::parse_with(r#" MaTh::Pi "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathPi));

	let out = syn::parse_with(r#" MaTh::InfiniTy "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathInfinity));

	let out = syn::parse_with(r#" MaTh::Neg_InfiniTy "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::MathNegInfinity));

	let out = syn::parse_with(r#" TiME::ePoCH "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert_eq!(out, Expr::Constant(Constant::TimeEpoch));
}

#[test]
fn scientific_decimal() {
	let res = syn::parse_with(r#" 9.7e-7dec "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert!(matches!(res, Expr::Literal(Literal::Decimal(_))));
	assert_eq!(res.to_sql(), "0.00000097dec")
}

#[test]
fn scientific_number() {
	let res = syn::parse_with(r#" 9.7e-5"#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	assert!(matches!(res, Expr::Literal(Literal::Float(_))));
	assert_eq!(res.to_sql(), "0.000097f")
}

#[test]
fn number_method() {
	let res = syn::parse_with(r#" 9.7e-5.sin()"#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	let expected = Expr::Idiom(Idiom(vec![
		Part::Start(Expr::Literal(Literal::Float(9.7e-5))),
		Part::Method("sin".into(), vec![]),
	]));
	assert_eq!(res, expected);

	let res = syn::parse_with(r#" 1.sin()"#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap();
	let expected = Expr::Idiom(Idiom(vec![
		Part::Start(Expr::Literal(Literal::Integer(1))),
		Part::Method("sin".into(), vec![]),
	]));
	assert_eq!(res, expected);
}

#[test]
fn datetime_error() {
	syn::parse_with(r#" d"2001-01-01T01:01:01.9999999999" "#.as_bytes(), async |parser, stk| {
		parser.parse_expr_field(stk).await
	})
	.unwrap_err();
}

#[test]
fn empty_string() {
	syn::parse_with("".as_bytes(), async |parser, stk| parser.parse_expr_field(stk).await)
		.unwrap_err();
}