#![allow(clippy::needless_borrow)]
use reddb_server::storage::query::ast::{
CompareOp, CreatePolicyQuery, Expr, Filter, QueryExpr, TableQuery,
};
use reddb_server::storage::query::modes::parse_multi;
use reddb_server::storage::query::parser;
use reddb_server::storage::query::planner::shape::{
bind_parameterized_query, parameterize_query_expr,
};
use reddb_server::storage::schema::Value;
fn extract_where_expr(tq: &TableQuery) -> Expr {
if let Some(expr) = &tq.where_expr {
return expr.clone();
}
panic!("table query has no where_expr after binding");
}
#[test]
fn prepared_bound_string_is_treated_as_literal_not_sql() {
let parsed =
parse_multi("SELECT * FROM users WHERE name = 'placeholder'").expect("parse base query");
let parameterised = parameterize_query_expr(&parsed).expect("query is parameterisable");
assert_eq!(
parameterised.parameter_count, 1,
"exactly one literal should have been parameterised"
);
let payload = "'; DROP TABLE users; --";
let bound = bind_parameterized_query(
¶meterised.shape,
&[Value::text(std::sync::Arc::<str>::from(payload))],
parameterised.parameter_count,
)
.expect("bind succeeds");
let table = match bound {
QueryExpr::Table(t) => t,
other => panic!("expected Table, got {:?}", other),
};
let where_expr = extract_where_expr(&table);
let (lhs, rhs) = match where_expr {
Expr::BinaryOp { lhs, rhs, .. } => (lhs, rhs),
other => panic!("expected BinaryOp, got {:?}", other),
};
assert!(
matches!(*lhs, Expr::Column { .. }),
"lhs should be a column reference"
);
let bound_value = match *rhs {
Expr::Literal { value, .. } => value,
other => panic!("rhs should be a Literal post-bind, got {:?}", other),
};
let s = match &bound_value {
Value::Text(s) => s.as_ref(),
other => panic!("bound value should be String, got {:?}", other),
};
assert_eq!(s, payload, "bound payload must round-trip byte-for-byte");
assert!(
s.contains("DROP TABLE"),
"the injection text is preserved as opaque string content"
);
}
#[test]
fn identifier_with_sql_metacharacters_is_rejected_at_parse() {
let result = parser::parse(r#"CREATE TABLE "users; DROP TABLE x" (id INT)"#);
let err = result.expect_err("must be a parse error");
let msg = err.to_string().to_lowercase();
assert!(
msg.contains("ident") || msg.contains("identifier") || msg.contains("expected"),
"error must point at the identifier rule, got: {}",
err
);
let result = parser::parse("CREATE TABLE 'evil; DROP TABLE x' (id INT)");
assert!(
result.is_err(),
"single-quoted identifier must also be rejected"
);
let ok = parser::parse("CREATE TABLE legit_users (id INT)");
assert!(ok.is_ok(), "non-injection identifier must parse: {:?}", ok);
}
#[test]
fn rls_policy_body_with_comment_metacharacters_parses_as_literal() {
let sql = "CREATE POLICY p ON users FOR SELECT TO reader USING (tenant = '''; -- ')";
let parsed = parse_multi(sql).expect("parse CREATE POLICY");
let policy: CreatePolicyQuery = match parsed {
QueryExpr::CreatePolicy(p) => p,
other => panic!("expected CreatePolicy, got {:?}", other),
};
let filter: Filter = *policy.using;
let value = match filter {
Filter::Compare {
field: _,
op: CompareOp::Eq,
value,
} => value,
other => panic!("expected Filter::Compare, got {:?}", other),
};
let s = match &value {
Value::Text(s) => s.as_ref(),
other => panic!("policy value should be String, got {:?}", other),
};
assert_eq!(
s, "'; -- ",
"literal must preserve injection-shaped bytes verbatim"
);
assert_eq!(policy.name, "p");
assert_eq!(policy.table, "users");
}
#[test]
fn ask_path_does_not_re_execute_llm_output_as_sql() {
let parsed = parse_multi("ASK 'who owns the users table'").expect("parse ASK");
let ask = match parsed {
QueryExpr::Ask(a) => a,
other => panic!("expected Ask, got {:?}", other),
};
assert_eq!(ask.question, "who owns the users table");
let _ = (
&ask.question,
&ask.collection,
&ask.depth,
&ask.limit,
&ask.min_score,
&ask.provider,
&ask.model,
);
let parsed = parse_multi("ASK '''; DROP TABLE users; --'").expect("parse ASK with payload");
let ask = match parsed {
QueryExpr::Ask(a) => a,
other => panic!("expected Ask, got {:?}", other),
};
assert_eq!(
ask.question, "'; DROP TABLE users; --",
"question text round-trips verbatim, no SQL parse on the payload"
);
}