use polars::prelude::PolarsError;
use sqlparser::ast::Statement;
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::{Parser, ParserError, ParserOptions};
const PARSER_DEFAULT_DEPTH: usize = 50;
fn depth(limit: usize) -> usize {
if limit == 0 {
PARSER_DEFAULT_DEPTH
} else {
limit.min(PARSER_DEFAULT_DEPTH)
}
}
pub(crate) fn statements(sql: &str) -> Result<Vec<Statement>, ParserError> {
Parser::new(&GenericDialect)
.with_options(ParserOptions {
trailing_commas: true,
..Default::default()
})
.with_recursion_limit(depth(crate::limits::get().sql_depth))
.try_with_sql(sql)?
.parse_statements()
}
pub(crate) fn check(sql: &str) -> Result<(), PolarsError> {
match statements(sql) {
Ok(_) => Ok(()),
Err(ParserError::RecursionLimitExceeded) => Err(PolarsError::SQLInterface(
format!(
"The statement nests more than {} levels deep {} limits.sql_depth raises it",
depth(crate::limits::get().sql_depth),
crate::glyphs::get().middot
)
.into(),
)),
Err(e) => Err(PolarsError::SQLInterface(e.to_string().into())),
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub(crate) const FUZZ_HANG: &str = "SELECT a.deptCO(((((UN.a.d.da.d.dWN.COUN.a.d.dadeptCOa.d.da.d.dW.d.dWN.COUN.a.d.da.d.dW.d.dWN.a.d.[da=.d.da.d.dWIAIASR";
const DEEP_TAIL: &str = "a.f(((a.d.a.d.a.d.a.d.a.d.a.d.a.d.a.d.a.d.a.d.a.d.a.d.d.[x=";
#[test]
fn a_statement_past_the_bound_is_refused_naming_the_key() {
let message = check(FUZZ_HANG).expect_err("refused").to_string();
assert!(message.contains("more than 24 levels deep"), "{message}");
assert!(message.ends_with("limits.sql_depth raises it"), "{message}");
assert_eq!(
statements(FUZZ_HANG),
Err(ParserError::RecursionLimitExceeded)
);
}
#[test]
fn a_deep_statement_behind_case_or_not_is_refused_too() {
for sql in [
format!("SELECT CASE WHEN {DEEP_TAIL}"),
format!("SELECT NOT {DEEP_TAIL}"),
] {
let error = check(&sql).expect_err("refused");
assert!(
matches!(error, PolarsError::SQLInterface(_)),
"{sql}: {error:?}"
);
assert!(
error.to_string().starts_with("sql parser error:"),
"{sql}: {error}"
);
}
}
#[test]
fn statements_within_the_bound_pass_or_fail_as_polars_sql_reads_them() {
let error = check("SELECT f((((x.y.")
.expect_err("a mistake")
.to_string();
assert!(error.starts_with("sql parser error:"), "{error}");
assert!(!error.contains("limits.sql_depth"), "{error}");
assert!(check("SELECT dept, COUNT(*) FROM df GROUP BY dept").is_ok());
assert!(
check(
"SELECT * FROM df WHERE x IN (SELECT x FROM df WHERE y > (SELECT AVG(y) FROM df))"
)
.is_ok()
);
assert!(check("SELECT f(g(h(i(j(k(l(m(n(o(1)))))))))) FROM df").is_ok());
assert!(check("SELECT a.b.c.d.e.f.g.h FROM df").is_ok());
}
#[test]
fn zero_or_more_than_the_parsers_own_bound_means_that_bound() {
assert_eq!(depth(0), PARSER_DEFAULT_DEPTH);
assert_eq!(depth(24), 24);
assert_eq!(depth(60), PARSER_DEFAULT_DEPTH);
}
}