use super::ParseError;
use crate::cnf::MAX_COMPUTATION_DEPTH;
use nom::Err;
use std::cell::Cell;
use std::thread::panicking;
thread_local! {
static DEPTH: Cell<u8> = Cell::default();
}
const DEPTH_PER_DIVE: u8 = 4;
#[inline(never)]
pub(super) fn reset() {
DEPTH.with(|cell| {
debug_assert_eq!(cell.get(), 0, "previous parsing stopped abruptly");
cell.set(0)
});
}
#[inline(never)]
#[must_use = "must store and implicitly drop when returning"]
pub(crate) fn dive<I>(position: I) -> Result<Diving, Err<ParseError<I>>> {
DEPTH.with(|cell| {
let depth = cell.get().saturating_add(DEPTH_PER_DIVE);
if depth <= *MAX_COMPUTATION_DEPTH {
cell.replace(depth);
Ok(Diving)
} else {
Err(Err::Failure(ParseError::ExcessiveDepth(position)))
}
})
}
#[must_use]
#[non_exhaustive]
pub(crate) struct Diving;
impl Drop for Diving {
fn drop(&mut self) {
DEPTH.with(|cell| {
if let Some(depth) = cell.get().checked_sub(DEPTH_PER_DIVE) {
cell.replace(depth);
} else {
debug_assert!(panicking());
}
});
}
}
#[cfg(test)]
mod tests {
use super::super::{super::super::syn, query};
use super::*;
use crate::{Query, Value};
use nom::Finish;
use serde::Serialize;
use std::{
collections::HashMap,
time::{Duration, Instant},
};
#[test]
fn no_ending() {
let sql = "SELECT * FROM test";
syn::parse(sql).unwrap();
}
#[test]
fn parse_query_string() {
let sql = "SELECT * FROM test;";
syn::parse(sql).unwrap();
}
#[test]
fn trim_query_string() {
let sql = " SELECT * FROM test ; ";
syn::parse(sql).unwrap();
}
#[test]
fn parse_complex_rubbish() {
let sql = " SELECT * FROM test ; /* shouldbespace */ ;;; ";
syn::parse(sql).unwrap();
}
#[test]
fn parse_complex_failure() {
let sql = " SELECT * FROM { }} ";
syn::parse(sql).unwrap_err();
}
#[test]
fn parse_ok_recursion() {
let sql = "SELECT * FROM ((SELECT * FROM (5))) * 5;";
syn::parse(sql).unwrap();
}
#[test]
fn parse_ok_recursion_deeper() {
let sql = "SELECT * FROM (((( SELECT * FROM ((5)) + ((5)) + ((5)) )))) * ((( function() {return 5;} )));";
let start = Instant::now();
syn::parse(sql).unwrap();
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(2000),
"took {}ms, previously took ~1000ms in debug",
elapsed.as_millis()
)
}
#[test]
fn parse_ok_recursion_ifelse() {
let depth = 20;
let sql = format!("{} {}", "IF A {".repeat(depth), "}".repeat(depth));
syn::parse(&sql).unwrap();
}
#[test]
fn parse_ko_recursion_ifelse() {
use crate::err::Error;
let depth = 2000;
let sql = format!("{} {}", "IF A {".repeat(depth), "}".repeat(depth));
let err = syn::parse(&sql).unwrap_err();
assert!(
matches!(err, Error::InvalidQuery(_)),
"expected invalid query due to computation depth exceeded, got {:?}",
err
);
}
#[test]
fn parse_ok_recursion_relate() {
let depth = 20;
let sql =
format!("{} {} {}", "(RELATE ".repeat(depth), "a:1", " -> b:1 -> c:1)".repeat(depth));
syn::parse(&sql).unwrap();
}
#[test]
fn parse_ko_recursion_relate() {
use crate::err::Error;
let depth = 2000;
let sql =
format!("{} {} {}", "(RELATE ".repeat(depth), "a:1", " -> b:1 -> c:1)".repeat(depth));
let err = syn::parse(&sql).unwrap_err();
assert!(
matches!(err, Error::InvalidQuery(_)),
"expected invalid query due to computation depth exceeded, got {:?}",
err
);
}
#[test]
fn parse_ok_recursion_basic_idiom() {
let depth = 2;
let sql = format!("{}{}", "[a".repeat(depth), "]".repeat(depth));
syn::parse(&sql).unwrap();
}
#[test]
fn parse_ko_recursion_basic_idiom() {
use crate::err::Error;
let depth = 2000;
let sql = format!("{}{}", "[a".repeat(depth), "]".repeat(depth));
let err = syn::parse(&sql).unwrap_err();
assert!(
matches!(err, Error::InvalidQuery(_)),
"expected invalid query due to computation depth exceeded, got {:?}",
err
);
}
#[test]
fn parse_recursion_cast() {
for n in [10, 100, 500] {
recursive("SELECT * FROM ", "<int>", "5", "", n, n > 50);
}
}
#[test]
fn parse_recursion_geometry() {
for n in [1, 50, 100] {
recursive(
"SELECT * FROM ",
r#"{type: "GeometryCollection",geometries: ["#,
r#"{type: "MultiPoint",coordinates: [[10.0, 11.2],[10.5, 11.9]]}"#,
"]}",
n,
n > 25,
);
}
}
#[test]
fn parse_recursion_javascript() {
for n in [10, 1000] {
recursive("SELECT * FROM ", "function() {", "return 5;", "}", n, n > 500);
}
}
#[test]
fn parse_recursion_mixed() {
for n in [3, 15, 75] {
recursive("", "SELECT * FROM ((((", "5 * 5", ")))) * 5", n, n > 5);
}
}
#[test]
fn parse_recursion_select() {
for n in [5, 10, 100] {
recursive("SELECT * FROM ", "(SELECT * FROM ", "5", ")", n, n > 15);
}
}
#[test]
fn parse_recursion_value_subquery() {
for p in 1..=4 {
recursive("SELECT * FROM ", "(", "5", ")", 10usize.pow(p), p > 1);
}
}
#[test]
fn parse_recursion_if_subquery() {
for p in 1..=3 {
recursive("SELECT * FROM ", "IF true THEN ", "5", " ELSE 4 END", 6usize.pow(p), p > 1);
}
}
#[test]
fn parser_try() {
let sql = "
SELECT
*,
tags[$].value,
3s as duration,
1.345 AS number,
test AS `some thing`,
'2012-04-23T18:25:43.511Z' AS utctime,
'2012-04-23T18:25:43.511-08:00' AS pacifictime,
{ key: (3 + 1 + 2), other: 9 * 7, 'some thing': { otherkey: 'text', } } AS object
FROM $param, test, temp, test:thingy, |test:10|, |test:1..10|
WHERE IF true THEN 'YAY' ELSE 'OOPS' END
AND (0.1341, 0.5719) INSIDE { type: 'Polygon', coordinates: [[[0.1341, 0.5719], [0.1341, 0.5719]]] }
AND (3 + 3 * 4)=6
AND 3 + 3 * 4 = 6
AND ages CONTAINS 18
AND if IS true
SPLIT test.things
VERSION '2019-01-01T08:00:00Z'
TIMEOUT 2w;
CREATE person SET name = 'Tobie', age += 18;
";
let tmp = syn::parse(sql).unwrap();
let enc: Vec<u8> = Vec::from(&tmp);
let dec: Query = Query::from(enc);
assert_eq!(tmp, dec);
}
#[test]
fn parser_full() {
let sql = std::fs::read("test.surql").unwrap();
let sql = std::str::from_utf8(&sql).unwrap();
let res = syn::parse(sql);
let tmp = res.unwrap();
let enc: Vec<u8> = Vec::from(&tmp);
let dec: Query = Query::from(enc);
assert_eq!(tmp, dec);
}
#[test]
#[cfg_attr(debug_assertions, ignore)]
fn json_benchmark() {
#[derive(Clone, Serialize)]
struct Data {
boolean: bool,
integer: i32,
decimal: f32,
string: String,
inner: Option<Box<Self>>,
inners: Vec<Self>,
inner_map: HashMap<String, Self>,
}
let inner = Data {
boolean: true,
integer: -1,
decimal: 0.5,
string: "foo".to_owned(),
inner: None,
inners: Vec::new(),
inner_map: HashMap::new(),
};
let inners = vec![inner.clone(); 10];
let data = Data {
boolean: false,
integer: 42,
decimal: 9000.0,
string: "SurrealDB".to_owned(),
inner_map: inners.iter().enumerate().map(|(i, d)| (i.to_string(), d.clone())).collect(),
inners,
inner: Some(Box::new(inner)),
};
let json = serde_json::to_string(&data).unwrap();
let json_pretty = serde_json::to_string_pretty(&data).unwrap();
let benchmark = |de: fn(&str) -> Value| {
let time = Instant::now();
const ITERATIONS: u32 = 32;
for _ in 0..ITERATIONS {
std::hint::black_box(de(std::hint::black_box(&json)));
std::hint::black_box(de(std::hint::black_box(&json_pretty)));
}
time.elapsed().as_secs_f32() / (2 * ITERATIONS) as f32
};
println!("crate::json took {:.10}s/iter", benchmark(|s| crate::json(s).unwrap()));
}
fn recursive(
prefix: &str,
recursive_start: &str,
base: &str,
recursive_end: &str,
n: usize,
excessive: bool,
) {
let mut sql = String::from(prefix);
for _ in 0..n {
sql.push_str(recursive_start);
}
sql.push_str(base);
for _ in 0..n {
sql.push_str(recursive_end);
}
let start = Instant::now();
let res = query(&sql).finish();
let elapsed = start.elapsed();
if excessive {
assert!(
matches!(res, Err(ParseError::ExcessiveDepth(_))),
"expected computation depth exceeded, got {:?}",
res
);
} else {
res.unwrap();
}
let cutoff = if excessive {
500
} else {
1000
};
assert!(
elapsed < Duration::from_millis(cutoff),
"took {}ms, previously much faster to parse {n} in debug mode",
elapsed.as_millis()
)
}
}