use std::cell::Cell;
use lora_ast::limits::{MAX_CLAUSES, MAX_EXPRESSION_DEPTH, MAX_QUERY_NESTING};
use crate::errors::ParseError;
pub(super) fn check_nesting(input: &str) -> Result<usize, ParseError> {
let bytes = input.as_bytes();
let mut depth = 0usize;
let mut deepest = 0usize;
let mut list_types = 0usize;
let mut prev_byte = 0u8;
let mut prev_word: &[u8] = b"";
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
match b {
b'\'' | b'"' => {
i = skip_string(bytes, i);
prev_byte = b;
prev_word = b"";
continue;
}
b'`' => {
i = match memchr(b'`', &bytes[i + 1..]) {
Some(at) => i + 1 + at + 1,
None => bytes.len(),
};
prev_byte = b'`';
prev_word = b"";
continue;
}
b'/' if bytes.get(i + 1) == Some(&b'/') => {
i = match memchr(b'\n', &bytes[i..]) {
Some(at) => i + at + 1,
None => bytes.len(),
};
continue;
}
b'/' if bytes.get(i + 1) == Some(&b'*') => {
i = match find(&bytes[i + 2..], b"*/") {
Some(at) => i + 2 + at + 2,
None => bytes.len(),
};
continue;
}
b'(' | b'[' | b'{' => {
depth += 1;
if depth > MAX_QUERY_NESTING {
return Err(too_deep(i));
}
deepest = deepest.max(depth);
}
b')' | b']' | b'}' => depth = depth.saturating_sub(1),
b'<' if prev_word.eq_ignore_ascii_case(b"LIST") => {
list_types += 1;
depth += 1;
if depth > MAX_QUERY_NESTING {
return Err(too_deep(i));
}
deepest = deepest.max(depth);
}
b'>' if list_types > 0 => {
list_types -= 1;
depth = depth.saturating_sub(1);
}
b if b.is_ascii_alphabetic() || b == b'_' => {
let start = i;
while i < bytes.len() && is_ident_part(bytes[i]) {
i += 1;
}
let word = &bytes[start..i];
if prev_byte != b'.' && prev_byte != b':' {
if word.eq_ignore_ascii_case(b"CASE") {
depth += 1;
if depth > MAX_QUERY_NESTING {
return Err(too_deep(start));
}
deepest = deepest.max(depth);
} else if word.eq_ignore_ascii_case(b"END") && closes_case(prev_byte, prev_word)
{
depth = depth.saturating_sub(1);
}
}
prev_byte = bytes[i - 1];
prev_word = word;
continue;
}
b if b.is_ascii_whitespace() => {
i += 1;
continue;
}
_ => {}
}
prev_byte = b;
prev_word = b"";
i += 1;
}
Ok(deepest)
}
pub(super) fn parse_stack(nesting: usize) -> usize {
let (base, per_level) = if cfg!(debug_assertions) {
(1 << 20, 160 << 10)
} else {
(128 << 10, 32 << 10)
};
base + nesting * per_level
}
fn closes_case(prev_byte: u8, prev_word: &[u8]) -> bool {
const EXPRESSION_STARTS_AFTER: &[&[u8]] = &[
b"CASE",
b"WHEN",
b"THEN",
b"ELSE",
b"AS",
b"AND",
b"OR",
b"XOR",
b"NOT",
b"IN",
b"IS",
b"CONTAINS",
b"WITH",
b"RETURN",
b"WHERE",
b"BY",
b"DISTINCT",
b"UNWIND",
b"SKIP",
b"LIMIT",
b"SET",
b"DELETE",
b"YIELD",
];
if prev_word.is_empty() {
return prev_byte.is_ascii_digit()
|| matches!(prev_byte, b')' | b']' | b'}' | b'\'' | b'"' | b'`' | b'*');
}
!EXPRESSION_STARTS_AFTER
.iter()
.any(|kw| prev_word.eq_ignore_ascii_case(kw))
}
fn is_ident_part(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_' || b == b'$'
}
fn skip_string(bytes: &[u8], start: usize) -> usize {
let quote = bytes[start];
let mut i = start + 1;
while i < bytes.len() {
match bytes[i] {
b'\\' => i += 2,
b if b == quote => return i + 1,
_ => i += 1,
}
}
bytes.len()
}
fn memchr(needle: u8, haystack: &[u8]) -> Option<usize> {
haystack.iter().position(|&b| b == needle)
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}
fn too_deep(at: usize) -> ParseError {
ParseError::new(
format!(
"query nests brackets, braces, CASE expressions or LIST types more than \
{MAX_QUERY_NESTING} levels deep"
),
at,
at + 1,
)
}
thread_local! {
static EXPRESSION_DEPTH: Cell<usize> = const { Cell::new(0) };
static CLAUSES: Cell<usize> = const { Cell::new(0) };
}
pub(super) struct DepthGuard {
counter: &'static std::thread::LocalKey<Cell<usize>>,
cost: usize,
}
impl DepthGuard {
pub(super) fn expression(cost: usize, start: usize, end: usize) -> Result<Self, ParseError> {
Self::enter(&EXPRESSION_DEPTH, cost, MAX_EXPRESSION_DEPTH, || {
ParseError::new(
format!(
"expression nests more than {MAX_EXPRESSION_DEPTH} operators deep; \
split it up, for example with WITH or a list function"
),
start,
end,
)
})
}
pub(super) fn clauses(cost: usize, start: usize, end: usize) -> Result<Self, ParseError> {
Self::enter(&CLAUSES, cost, MAX_CLAUSES, || {
ParseError::new(
format!("query has more than {MAX_CLAUSES} clauses"),
start,
end,
)
})
}
fn enter(
counter: &'static std::thread::LocalKey<Cell<usize>>,
cost: usize,
max: usize,
error: impl FnOnce() -> ParseError,
) -> Result<Self, ParseError> {
let used = counter.with(Cell::get);
if cost > max.saturating_sub(used) {
return Err(error());
}
counter.with(|c| c.set(used + cost));
Ok(Self { counter, cost })
}
}
impl Drop for DepthGuard {
fn drop(&mut self) {
let cost = self.cost;
self.counter.with(|c| c.set(c.get() - cost));
}
}
pub(super) fn reset_budgets() {
EXPRESSION_DEPTH.with(|c| c.set(0));
CLAUSES.with(|c| c.set(0));
}