use super::types::*;
use crate::error::{Error, Result};
use nom::{
branch::alt,
bytes::complete::{tag, take_while1},
character::complete::char,
combinator::{opt, value},
multi::{many0, separated_list0},
sequence::preceded,
IResult,
};
use percent_encoding::percent_decode_str;
pub fn parse_query_params(query: &str, is_rpc: bool) -> Result<QueryParams> {
let mut params = QueryParams::default();
if query.is_empty() {
return Ok(params);
}
let mut pairs: Vec<(&str, &str)> = query
.split('&')
.filter_map(|pair| {
let mut parts = pair.splitn(2, '=');
Some((parts.next()?, parts.next().unwrap_or("")))
})
.collect();
pairs.sort_by_key(|(k, _)| *k);
params.canonical = pairs
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
for (key, value) in pairs {
let decoded_value = percent_decode_str(value)
.decode_utf8()
.map_err(|_| Error::InvalidQueryParam(key.into()))?
.to_string();
let decoded_key = percent_decode_str(key)
.decode_utf8()
.map_err(|_| Error::InvalidQueryParam(key.into()))?
.to_string();
let key: &str = &decoded_key;
if let Some((path, modifier)) = parse_modifier_key(key) {
match modifier {
Modifier::Order => {
let (_, terms) = parse_order_param(&decoded_value)?;
params.order.push((path, terms));
}
Modifier::Limit => {
let limit: i64 = decoded_value
.parse()
.map_err(|_| Error::InvalidQueryParam("limit".into()))?;
if limit < 0 {
return Err(Error::InvalidRange(
"Limit should be greater than or equal to zero.".into(),
));
}
params.ranges.entry(path.join(".")).or_default().limit = Some(limit);
}
Modifier::Offset => {
let offset: i64 = decoded_value
.parse()
.map_err(|_| Error::InvalidQueryParam("offset".into()))?;
let range = params.ranges.entry(path.join(".")).or_default();
range.offset = offset;
range.offset_explicit = true;
}
Modifier::Logic(op, negated) => {
params
.logic
.push((path, parse_logic_param(op, negated, &decoded_value)?));
}
}
continue;
}
match key {
"select" => {
params.select = parse_select(&decoded_value)?;
}
"columns" => {
params.columns = Some(parse_columns(&decoded_value)?.into_iter().collect());
}
"on_conflict" => {
params.on_conflict = Some(
decoded_value
.split(',')
.map(|s| s.trim().to_string())
.collect(),
);
}
key if !key.starts_with('_') => {
if is_rpc {
params.params.push((key.to_string(), decoded_value.clone()));
}
let parsed = parse_filter_param(key, &decoded_value);
let (path, filter) = match parsed {
Ok(parsed) => parsed,
Err(_) if is_rpc => continue,
Err(e) => return Err(e),
};
if path.is_empty() {
params.filter_fields.insert(filter.field.name.clone());
params.filters_root.push(filter);
} else {
params.filters.push((path, filter));
}
}
_ => {
params.params.push((key.to_string(), decoded_value));
}
}
}
Ok(params)
}
fn parse_columns(input: &str) -> Result<Vec<String>> {
const EXPECTED: &str = "expecting field name (* or [a..z0..9_$])";
let mut columns = Vec::new();
let mut at = 0usize;
for (index, field) in input.split(',').enumerate() {
if index > 0 {
at += 1;
}
let trimmed = field.trim();
if trimmed.is_empty() {
return Err(Error::UnparsableQuery {
kind: "columns parameter",
text: input.to_string(),
column: at + 1,
expected: match at >= input.len() {
true => format!("unexpected end of input {}", EXPECTED),
false => format!(
"unexpected {:?} {}",
input[at..].chars().next().unwrap_or_default(),
EXPECTED
),
},
});
}
columns.push(trimmed.to_string());
at += field.len();
}
Ok(columns)
}
pub fn parse_select(input: &str) -> Result<Vec<SelectItem>> {
if input.is_empty() {
return Ok(vec![]);
}
match parse_select_items(input) {
Ok(("", items)) => Ok(items),
_ => Err(Error::InvalidQueryParam(format!("select={}", input))),
}
}
fn parse_select_items(input: &str) -> IResult<&str, Vec<SelectItem>> {
separated_list0(char(','), parse_select_item)(input)
}
fn parse_select_item(input: &str) -> IResult<&str, SelectItem> {
let (input, _) = nom::character::complete::space0(input)?;
alt((
parse_star,
parse_bare_aggregate,
parse_spread_relation,
parse_relation_select,
parse_field_select,
))(input)
}
fn parse_star(input: &str) -> IResult<&str, SelectItem> {
let (input, _) = char('*')(input)?;
Ok((input, SelectItem::field("*")))
}
fn parse_bare_aggregate(input: &str) -> IResult<&str, SelectItem> {
let (input, alias) = opt(parse_alias_prefix)(input)?;
let (input, _) = tag("count()")(input)?;
let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
Ok((
input,
SelectItem::Field {
field: Field::simple(""),
aggregate: Some(AggregateFunction::Count),
aggregate_cast: cast.map(|s| s.to_string()),
cast: None,
alias: alias.map(|s| s.to_string()),
},
))
}
fn parse_aggregate_suffix(input: &str) -> IResult<&str, AggregateFunction> {
let (input, _) = char('.')(input)?;
let (input, function) = alt((
value(AggregateFunction::Sum, tag("sum")),
value(AggregateFunction::Avg, tag("avg")),
value(AggregateFunction::Max, tag("max")),
value(AggregateFunction::Min, tag("min")),
value(AggregateFunction::Count, tag("count")),
))(input)?;
let (input, _) = tag("()")(input)?;
Ok((input, function))
}
fn parse_spread_relation(input: &str) -> IResult<&str, SelectItem> {
let (input, _) = tag("...")(input)?;
let (input, relation) = parse_field_name(input)?;
let (input, (hint, join_type)) = parse_relation_modifiers(input)?;
let (input, _) = char('(')(input)?;
let (input, nested) = parse_nested_select(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
SelectItem::SpreadRelation {
relation,
hint,
join_type,
select: nested,
},
))
}
fn parse_nested_select(input: &str) -> IResult<&str, Vec<SelectItem>> {
let mut depth = 0usize;
let mut end = input.len();
for (idx, ch) in input.char_indices() {
match ch {
'(' => depth += 1,
')' => {
if depth == 0 {
end = idx;
break;
}
depth -= 1;
}
_ => {}
}
}
let (body, rest) = input.split_at(end);
if body.is_empty() {
return Ok((rest, Vec::new()));
}
match parse_select_items(body) {
Ok(("", items)) => Ok((rest, items)),
_ => Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
))),
}
}
fn parse_relation_select(input: &str) -> IResult<&str, SelectItem> {
let (input, alias) = opt(parse_alias_prefix)(input)?;
let (input, name) = parse_field_name(input)?;
let (input, (hint, join_type)) = parse_relation_modifiers(input)?;
let (input, _) = char('(')(input)?;
let (input, nested) = parse_nested_select(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
SelectItem::Relation {
relation: name,
alias,
hint,
join_type,
select: nested,
},
))
}
fn parse_field_select(input: &str) -> IResult<&str, SelectItem> {
let (input, alias) = opt(parse_alias_prefix)(input)?;
let (input, name) = parse_field_name(input)?;
let (input, json_path) = parse_json_path(input)?;
let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
let (input, aggregate) = opt(parse_aggregate_suffix)(input)?;
let (input, aggregate_cast) = if aggregate.is_some() {
let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
(input, cast.map(|s| s.to_string()))
} else {
(input, None)
};
Ok((
input,
SelectItem::Field {
field: Field { name, json_path },
aggregate,
aggregate_cast,
cast: cast.map(|s| s.to_string()),
alias: alias.map(|s| s.to_string()),
},
))
}
fn parse_alias_prefix(input: &str) -> IResult<&str, String> {
let (rest, name) = parse_field_name(input)?;
let (rest, _) = char(':')(rest)?;
if rest.starts_with(':') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
Ok((rest, name))
}
fn parse_relation_modifiers(input: &str) -> IResult<&str, (Option<String>, Option<JoinType>)> {
let (input, modifiers) = many0(preceded(char('!'), parse_identifier))(input)?;
let mut hint = None;
let mut join_type = None;
for modifier in modifiers {
match modifier {
"inner" => join_type = Some(JoinType::Inner),
"left" => join_type = Some(JoinType::Left),
other => hint = Some(other.to_string()),
}
}
Ok((input, (hint, join_type)))
}
fn parse_filter_param(key: &str, value: &str) -> Result<(EmbedPath, Filter)> {
let (path, field) = parse_filter_key(key)?;
let op_expr = parse_filter_value(value)?;
Ok((path, Filter::new(field, op_expr)))
}
fn split_json_path(reference: &str) -> Field {
let Some(arrow) = reference.find("->") else {
return Field::simple(reference);
};
let (column, rest) = reference.split_at(arrow);
match parse_json_path(rest) {
Ok(("", json_path)) if !json_path.is_empty() => Field::with_json_path(column, json_path),
_ => Field::simple(reference),
}
}
fn parse_filter_key(key: &str) -> Result<(EmbedPath, Field)> {
if let Some(parsed) = parse_field_reference(key) {
return Ok(parsed);
}
let mut parts: Vec<&str> = key.split('.').collect();
let field = parts
.pop()
.ok_or_else(|| Error::InvalidQueryParam(key.into()))?;
let path = parts.into_iter().map(String::from).collect();
Ok((path, split_json_path(field)))
}
fn parse_field_reference(key: &str) -> Option<(EmbedPath, Field)> {
let mut names = Vec::new();
let mut rest = key;
loop {
let (remainder, name) = parse_field_name(rest).ok()?;
names.push(name);
match remainder.strip_prefix('.') {
Some(after) => rest = after,
None => {
rest = remainder;
break;
}
}
}
let (rest, json_path) = parse_json_path(rest).ok()?;
if !rest.is_empty() {
return None;
}
let field = names.pop()?;
Some((names, Field::with_json_path(field, json_path)))
}
pub fn value_is_filter(value: &str) -> bool {
parse_filter_value(value).is_ok()
}
fn parse_filter_value(value: &str) -> Result<OpExpr> {
let whole = value;
let (value, negated) = if let Some(rest) = value.strip_prefix("not.") {
(rest, true)
} else {
(value, false)
};
let operation = parse_operation(value).map_err(|error| match error {
Error::InvalidQueryParam(_) => Error::UnparsableQuery {
kind: "filter",
text: whole.to_string(),
column: 1,
expected: format!(
"unexpected \"{}\" expecting \"not\" or operator (eq, gt, ...)",
whole.chars().next().unwrap_or_default()
),
},
other => other,
})?;
Ok(OpExpr { negated, operation })
}
fn quant_operator(name: &str) -> Option<QuantOperator> {
Some(match name {
"eq" => QuantOperator::Equal,
"gt" => QuantOperator::GreaterThan,
"gte" => QuantOperator::GreaterThanEqual,
"lt" => QuantOperator::LessThan,
"lte" => QuantOperator::LessThanEqual,
"like" => QuantOperator::Like,
"ilike" => QuantOperator::ILike,
"match" => QuantOperator::Match,
"imatch" => QuantOperator::IMatch,
_ => return None,
})
}
fn parse_quantifier(modifier: &str) -> Option<OpQuantifier> {
match modifier {
"any" => Some(OpQuantifier::Any),
"all" => Some(OpQuantifier::All),
_ => None,
}
}
fn parse_operation(value: &str) -> Result<Operation> {
if let Some((name, rest)) = value.split_once('(') {
if let Some((modifier, operand)) = rest.split_once(").") {
if let (Some(op), Some(quantifier)) = (quant_operator(name), parse_quantifier(modifier))
{
return Ok(Operation::Quant {
op,
quantifier: Some(quantifier),
value: operand.to_string(),
});
}
}
}
if let Some((name, rest)) = value.split_once('.') {
if let Some(op) = quant_operator(name) {
return Ok(Operation::Quant {
op,
quantifier: None,
value: rest.to_string(),
});
}
}
if let Some(rest) = value.strip_prefix("neq.") {
return Ok(Operation::Simple {
op: SimpleOperator::NotEqual,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("cs.") {
return Ok(Operation::Simple {
op: SimpleOperator::Contains,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("cd.") {
return Ok(Operation::Simple {
op: SimpleOperator::Contained,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("ov.") {
return Ok(Operation::Simple {
op: SimpleOperator::Overlap,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("sl.") {
return Ok(Operation::Simple {
op: SimpleOperator::StrictlyLeft,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("sr.") {
return Ok(Operation::Simple {
op: SimpleOperator::StrictlyRight,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("nxr.") {
return Ok(Operation::Simple {
op: SimpleOperator::NotExtendsRight,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("nxl.") {
return Ok(Operation::Simple {
op: SimpleOperator::NotExtendsLeft,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("adj.") {
return Ok(Operation::Simple {
op: SimpleOperator::Adjacent,
value: rest.to_string(),
});
}
if let Some(rest) = value.strip_prefix("in.") {
let values = parse_in_list(rest)?;
return Ok(Operation::In(values));
}
if let Some(rest) = value.strip_prefix("is.") {
let is_val = match rest.to_ascii_lowercase().as_str() {
"null" => IsValue::Null,
"not_null" => IsValue::NotNull,
"true" => IsValue::True,
"false" => IsValue::False,
"unknown" => IsValue::Unknown,
_ => return Err(unreadable_is_value(rest)),
};
return Ok(Operation::Is(is_val));
}
if let Some(rest) = value.strip_prefix("isdistinct.") {
return Ok(Operation::IsDistinctFrom(rest.to_string()));
}
if let Some(rest) = value.strip_prefix("fts") {
return parse_fts(FtsOperator::Fts, rest);
}
if let Some(rest) = value.strip_prefix("plfts") {
return parse_fts(FtsOperator::Plain, rest);
}
if let Some(rest) = value.strip_prefix("phfts") {
return parse_fts(FtsOperator::Phrase, rest);
}
if let Some(rest) = value.strip_prefix("wfts") {
return parse_fts(FtsOperator::Websearch, rest);
}
Err(Error::InvalidQueryParam(value.into()))
}
fn unreadable_order(term: &str, at: usize, part: &str) -> Error {
const MODIFIERS: [&str; 4] = ["asc", "desc", "nullsfirst", "nullslast"];
let read = MODIFIERS
.iter()
.map(|modifier| {
part.chars()
.zip(modifier.chars())
.take_while(|(a, b)| a == b)
.count()
})
.max()
.unwrap_or(0);
Error::UnparsableQuery {
kind: "order",
text: term.to_string(),
column: at + read + 1,
expected: format!(
"unexpected {:?} expecting \",\" or end of input",
part.chars().nth(read).unwrap_or_default()
),
}
}
fn unreadable_is_value(rest: &str) -> Error {
const VALUES: [&str; 5] = ["null", "not_null", "true", "false", "unknown"];
let lowered = rest.to_ascii_lowercase();
let read = VALUES
.iter()
.map(|value| {
lowered
.chars()
.zip(value.chars())
.take_while(|(a, b)| a == b)
.count()
})
.max()
.unwrap_or(0);
Error::UnparsableQuery {
kind: "filter",
text: format!("is.{}", rest),
column: 4 + read,
expected: format!(
"unexpected \"{}\" expecting isVal: ({})",
rest.chars().nth(read).unwrap_or_default(),
VALUES.join(", ")
),
}
}
fn parse_in_list(value: &str) -> Result<Vec<String>> {
let inner = value
.trim()
.strip_prefix('(')
.and_then(|s| s.strip_suffix(')'))
.map(|inner| {
inner
.trim_start_matches([' ', '\t'])
.trim_end_matches([' ', '\t'])
})
.ok_or_else(|| Error::InvalidQueryParam(format!("in.{}", value)))?;
if inner.is_empty() {
return Ok(Vec::new());
}
let mut values = Vec::new();
let mut rest = inner;
loop {
let element = match quoted_element(rest) {
Some((unquoted, remainder)) => {
rest = remainder;
unquoted
}
None => {
let end = rest.find(',').unwrap_or(rest.len());
let (element, remainder) = rest.split_at(end);
rest = remainder;
element.to_string()
}
};
values.push(element);
match rest.strip_prefix(',') {
Some(remainder) => rest = remainder,
None => break,
}
}
Ok(values)
}
fn quoted_element(input: &str) -> Option<(String, &str)> {
let body = input.strip_prefix('"')?;
let mut value = String::new();
let mut chars = body.char_indices();
while let Some((index, ch)) = chars.next() {
match ch {
'\\' => {
let (_, escaped) = chars.next()?;
value.push(escaped);
}
'"' => {
let rest = &body[index + 1..];
return match rest.is_empty() || rest.starts_with(',') {
true => Some((value, rest)),
false => None,
};
}
other => value.push(other),
}
}
None
}
fn parse_fts(op: FtsOperator, rest: &str) -> Result<Operation> {
if let Some(rest) = rest.strip_prefix('(') {
let (lang, query) = rest
.split_once(").")
.ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
return Ok(Operation::Fts {
op,
language: Some(lang.to_string()),
value: query.to_string(),
});
}
let query = rest
.strip_prefix('.')
.ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
Ok(Operation::Fts {
op,
language: None,
value: query.to_string(),
})
}
fn parse_order_param(value: &str) -> Result<(EmbedPath, Vec<OrderTerm>)> {
let terms: Vec<OrderTerm> = value
.split(',')
.map(|s| parse_order_term(s.trim()))
.collect::<Result<Vec<_>>>()?;
Ok((vec![], terms))
}
fn parse_order_term(value: &str) -> Result<OrderTerm> {
let parts: Vec<&str> = value.split('.').collect();
if parts.is_empty() {
return Err(Error::InvalidQueryParam("order".into()));
}
let field_name = parts[0];
let field = split_json_path(field_name);
let mut direction = None;
let mut nulls = None;
let mut at = field_name.len() + 1;
for part in &parts[1..] {
match *part {
"asc" => direction = Some(OrderDirection::Asc),
"desc" => direction = Some(OrderDirection::Desc),
"nullsfirst" => nulls = Some(OrderNulls::First),
"nullslast" => nulls = Some(OrderNulls::Last),
other => return Err(unreadable_order(value, at, other)),
}
at += part.len() + 1;
}
if let Some((relation, rest)) = field_name.split_once('(') {
if let Some(column) = rest.strip_suffix(')') {
return Ok(OrderTerm::Relation {
relation: relation.to_string(),
field: split_json_path(column),
direction,
nulls,
});
}
}
Ok(OrderTerm::Field {
field,
direction,
nulls,
})
}
fn parse_logic_param(op: LogicOperator, negated: bool, value: &str) -> Result<LogicTree> {
let trimmed = value.trim();
let inner = balanced_group(trimmed).ok_or_else(|| Error::InvalidQueryParam(value.into()))?;
let members = split_top_level(inner);
for member in &members {
if !member.trim().is_empty() {
continue;
}
let at = offset_within(trimmed, member);
return Err(Error::UnparsableQuery {
kind: "logic tree",
text: trimmed.to_string(),
column: negated as usize * "not.".len()
+ match op {
LogicOperator::And => 3,
LogicOperator::Or => 2,
}
+ at
+ 1,
expected: format!(
"unexpected \"{}\" expecting field name (* or [a..z0..9_$]), \
negation operator (not) or logic operator (and, or)",
trimmed[at..].chars().next().unwrap_or_default()
),
});
}
Ok(LogicTree::Expr {
negated,
op,
children: members
.into_iter()
.map(parse_logic_child)
.collect::<Result<Vec<_>>>()?,
})
}
fn offset_within(whole: &str, part: &str) -> usize {
(part.as_ptr() as usize).saturating_sub(whole.as_ptr() as usize)
}
fn balanced_group(input: &str) -> Option<&str> {
let body = input.strip_prefix('(')?;
let mut depth = 0usize;
let mut quoted = false;
let mut escaped = false;
for (idx, ch) in body.char_indices() {
if quoted {
match ch {
_ if escaped => escaped = false,
'\\' => escaped = true,
'"' => quoted = false,
_ => {}
}
continue;
}
match ch {
'"' => quoted = true,
'(' => depth += 1,
')' if depth == 0 => return Some(&body[..idx]),
')' => depth -= 1,
_ => {}
}
}
None
}
fn split_top_level(input: &str) -> Vec<&str> {
let mut parts = Vec::new();
let mut depth = 0usize;
let mut quoted = false;
let mut escaped = false;
let mut start = 0usize;
for (idx, ch) in input.char_indices() {
if quoted {
match ch {
_ if escaped => escaped = false,
'\\' => escaped = true,
'"' => quoted = false,
_ => {}
}
continue;
}
match ch {
'"' => quoted = true,
'(' | '{' | '[' => depth += 1,
')' | '}' | ']' => depth = depth.saturating_sub(1),
',' if depth == 0 => {
parts.push(&input[start..idx]);
start = idx + ch.len_utf8();
}
_ => {}
}
}
parts.push(&input[start..]);
parts
}
fn parse_logic_child(item: &str) -> Result<LogicTree> {
let item = item.trim();
let (body, negated) = match item.strip_prefix("not.") {
Some(rest) if rest.starts_with("and(") || rest.starts_with("or(") => (rest, true),
_ => (item, false),
};
for (name, op) in [("and", LogicOperator::And), ("or", LogicOperator::Or)] {
if let Some(rest) = body.strip_prefix(name) {
let rest = rest.trim_start();
if rest.starts_with('(') && rest.ends_with(')') {
return parse_logic_param(op, negated, rest);
}
}
}
let (key, val) = body
.split_once('.')
.ok_or_else(|| Error::InvalidQueryParam(item.into()))?;
let (_, filter) = parse_filter_param(key, &unquote_logic_operand(val))?;
Ok(LogicTree::Stmt(filter))
}
fn unquote_logic_operand(value: &str) -> String {
let Some(open) = value.find('"') else {
return value.to_string();
};
if open != 0 && !value[..open].ends_with('.') {
return value.to_string();
}
if !value.ends_with('"') || value.len() < open + 2 {
return value.to_string();
}
let mut unquoted = String::from(&value[..open]);
let mut chars = value[open + 1..value.len() - 1].chars();
while let Some(ch) = chars.next() {
match ch {
'\\' => {
if let Some(escaped) = chars.next() {
unquoted.push(escaped);
}
}
other => unquoted.push(other),
}
}
unquoted
}
enum Modifier {
Order,
Limit,
Offset,
Logic(LogicOperator, bool),
}
fn parse_modifier_key(key: &str) -> Option<(EmbedPath, Modifier)> {
let mut parts: Vec<&str> = key.split('.').collect();
let last = parts.pop()?;
let modifier = match last {
"order" => Modifier::Order,
"limit" => Modifier::Limit,
"offset" => Modifier::Offset,
"and" | "or" => {
let op = if last == "and" {
LogicOperator::And
} else {
LogicOperator::Or
};
let negated = parts.last() == Some(&"not");
if negated {
parts.pop();
}
Modifier::Logic(op, negated)
}
_ => return None,
};
Some((parts.into_iter().map(String::from).collect(), modifier))
}
fn parse_identifier(input: &str) -> IResult<&str, &str> {
let (rest, matched) =
take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '$' || c == ' ')(input)?;
Ok((rest, matched.trim()))
}
fn parse_field_name(input: &str) -> IResult<&str, String> {
if let Ok(quoted) = parse_quoted_name(input) {
return Ok(quoted);
}
let (mut rest, first) = parse_identifier(input)?;
let mut name = first.to_string();
while let Some(after_dash) = rest.strip_prefix('-') {
if after_dash.starts_with('>') {
break;
}
let Ok((next, segment)) = parse_identifier(after_dash) else {
break;
};
name.push('-');
name.push_str(segment);
rest = next;
}
Ok((rest, name))
}
fn parse_quoted_name(input: &str) -> IResult<&str, String> {
let (rest, _) = char('"')(input)?;
let mut name = String::new();
let mut chars = rest.char_indices();
while let Some((index, ch)) = chars.next() {
match ch {
'\\' => {
if let Some((_, escaped)) = chars.next() {
name.push(escaped);
}
}
'"' => return Ok((&rest[index + 1..], name)),
other => name.push(other),
}
}
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)))
}
fn parse_json_path(input: &str) -> IResult<&str, JsonPath> {
many0(alt((parse_double_arrow, parse_arrow)))(input)
}
fn parse_json_operand(input: &str) -> IResult<&str, JsonOperand> {
let mut end = input.len();
for (idx, ch) in input.char_indices() {
if matches!(ch, ',' | '(' | ')' | ':') || input[idx..].starts_with("->") {
end = idx;
break;
}
}
let (key, rest) = input.split_at(end);
if key.is_empty() {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::TakeWhile1,
)));
}
let operand = match key.parse::<i32>() {
Ok(index) => JsonOperand::Idx(index),
Err(_) if key.starts_with('-') => {
return Err(nom::Err::Failure(nom::error::Error::new(
&input[1..],
nom::error::ErrorKind::Digit,
)))
}
Err(_) => JsonOperand::Key(key.to_string()),
};
Ok((rest, operand))
}
fn parse_arrow(input: &str) -> IResult<&str, JsonOperation> {
let (input, _) = tag("->")(input)?;
let (input, operand) = parse_json_operand(input)?;
Ok((input, JsonOperation::Arrow(operand)))
}
fn parse_double_arrow(input: &str) -> IResult<&str, JsonOperation> {
let (input, _) = tag("->>")(input)?;
let (input, operand) = parse_json_operand(input)?;
Ok((input, JsonOperation::DoubleArrow(operand)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_filter() {
let params = parse_query_params("name=eq.John", false).unwrap();
assert_eq!(params.filters_root.len(), 1);
assert_eq!(params.filters_root[0].field.name, "name");
}
#[test]
fn test_parse_negated_filter() {
let params = parse_query_params("status=not.eq.active", false).unwrap();
assert!(params.filters_root[0].op_expr.negated);
}
#[test]
fn test_parse_in_filter() {
let params = parse_query_params("id=in.(1,2,3)", false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::In(values) => {
assert_eq!(values, &vec!["1", "2", "3"]);
}
_ => panic!("Expected In operation"),
}
}
#[test]
fn test_json_path_double_arrow_wins_over_single() {
let items = parse_select("settings->foo->>bar").unwrap();
match &items[0] {
SelectItem::Field { field, .. } => {
assert_eq!(
field.json_path,
vec![
JsonOperation::Arrow(JsonOperand::Key("foo".into())),
JsonOperation::DoubleArrow(JsonOperand::Key("bar".into())),
]
);
}
other => panic!("expected a field, got {:?}", other),
}
}
#[test]
fn test_json_path_accepts_awkward_keys() {
let items = parse_select("data->23-xy-45->>!@#$%^&*_e").unwrap();
match &items[0] {
SelectItem::Field { field, .. } => {
assert_eq!(
field.json_path,
vec![
JsonOperation::Arrow(JsonOperand::Key("23-xy-45".into())),
JsonOperation::DoubleArrow(JsonOperand::Key("!@#$%^&*_e".into())),
]
);
}
other => panic!("expected a field, got {:?}", other),
}
}
#[test]
fn test_json_path_digits_are_an_index_but_only_when_whole() {
let items = parse_select("data->0,other->0xy1").unwrap();
let paths: Vec<_> = items
.iter()
.map(|i| match i {
SelectItem::Field { field, .. } => field.json_path.clone(),
other => panic!("expected a field, got {:?}", other),
})
.collect();
assert_eq!(paths[0], vec![JsonOperation::Arrow(JsonOperand::Idx(0))]);
assert_eq!(
paths[1],
vec![JsonOperation::Arrow(JsonOperand::Key("0xy1".into()))]
);
}
#[test]
fn test_json_path_stops_at_a_cast() {
let items = parse_select("settings->>foo::json").unwrap();
match &items[0] {
SelectItem::Field { field, cast, .. } => {
assert_eq!(
field.json_path,
vec![JsonOperation::DoubleArrow(JsonOperand::Key("foo".into()))]
);
assert_eq!(cast.as_deref(), Some("json"));
}
other => panic!("expected a field, got {:?}", other),
}
}
#[test]
fn test_parse_spread_relation_keeps_its_columns() {
let items = parse_select("title,...directors(first_name,last_name)").unwrap();
assert_eq!(items.len(), 2);
match &items[1] {
SelectItem::SpreadRelation {
relation, select, ..
} => {
assert_eq!(relation, "directors");
let names: Vec<_> = select
.iter()
.map(|item| match item {
SelectItem::Field { field, .. } => field.name.clone(),
other => panic!("expected a field, got {:?}", other),
})
.collect();
assert_eq!(names, vec!["first_name", "last_name"]);
}
other => panic!("expected a spread relation, got {:?}", other),
}
}
#[test]
fn test_parse_spread_relation_does_not_swallow_later_items() {
let items = parse_select("...directors(name),year").unwrap();
assert_eq!(items.len(), 2);
assert!(matches!(items[0], SelectItem::SpreadRelation { .. }));
match &items[1] {
SelectItem::Field { field, .. } => assert_eq!(field.name, "year"),
other => panic!("expected a field, got {:?}", other),
}
}
#[test]
fn test_parse_nested_spread_relation() {
let items = parse_select("id,films(title,...directors(name))").unwrap();
match &items[1] {
SelectItem::Relation { select, .. } => {
assert_eq!(select.len(), 2);
assert!(matches!(select[1], SelectItem::SpreadRelation { .. }));
}
other => panic!("expected a relation, got {:?}", other),
}
}
#[test]
fn test_parse_in_filter_with_quoted_commas() {
let params = parse_query_params(r#"name=in.("hi,there","yes,you")"#, false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::In(values) => {
assert_eq!(values, &vec!["hi,there".to_string(), "yes,you".to_string()]);
}
other => panic!("expected an In operation, got {:?}", other),
}
}
#[test]
fn test_parse_in_filter_with_escapes() {
let params = parse_query_params(r#"name=in.("a\"b","c\\d")"#, false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::In(values) => {
assert_eq!(values, &vec![r#"a"b"#.to_string(), r#"c\d"#.to_string()]);
}
other => panic!("expected an In operation, got {:?}", other),
}
}
#[test]
fn test_parse_quantified_filter() {
for (query, expected) in [
("id=eq(any).{1,2,3}", OpQuantifier::Any),
("id=eq(all).{1,2,3}", OpQuantifier::All),
] {
let params = parse_query_params(query, false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::Quant {
op,
quantifier,
value,
} => {
assert_eq!(op, &QuantOperator::Equal);
assert_eq!(quantifier.as_ref(), Some(&expected));
assert_eq!(value, "{1,2,3}");
}
other => panic!("expected a quantified operation, got {:?}", other),
}
}
}
#[test]
fn test_parse_quantified_like_keeps_array_literal() {
let params = parse_query_params("name=like(any).{foo*,bar*}", false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::Quant {
op,
quantifier,
value,
} => {
assert_eq!(op, &QuantOperator::Like);
assert_eq!(quantifier.as_ref(), Some(&OpQuantifier::Any));
assert_eq!(value, "{foo*,bar*}");
}
other => panic!("expected a quantified operation, got {:?}", other),
}
}
#[test]
fn test_parse_negated_quantified_filter() {
let params = parse_query_params("id=not.eq(any).{1,2}", false).unwrap();
assert!(params.filters_root[0].op_expr.negated);
assert!(matches!(
params.filters_root[0].op_expr.operation,
Operation::Quant {
quantifier: Some(OpQuantifier::Any),
..
}
));
}
#[test]
fn test_quantifier_form_does_not_capture_fts_language() {
let params = parse_query_params("body=fts(english).cat", false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::Fts { language, .. } => {
assert_eq!(language.as_deref(), Some("english"));
}
other => panic!("expected an fts operation, got {:?}", other),
}
}
#[test]
fn test_parse_is_null() {
let params = parse_query_params("deleted_at=is.null", false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::Is(IsValue::Null) => {}
_ => panic!("Expected Is Null"),
}
}
#[test]
fn test_parse_order() {
let params = parse_query_params("order=name.asc,age.desc.nullslast", false).unwrap();
assert_eq!(params.order.len(), 1);
let (_, terms) = ¶ms.order[0];
assert_eq!(terms.len(), 2);
}
#[test]
fn test_parse_limit_offset() {
let params = parse_query_params("limit=10&offset=20", false).unwrap();
let range = params.ranges.get("").unwrap();
assert_eq!(range.limit, Some(10));
assert_eq!(range.offset, 20);
}
#[test]
fn test_parse_select() {
let items = parse_select("id,name,orders(id,amount)").unwrap();
assert_eq!(items.len(), 3);
}
#[test]
fn test_parse_fts() {
let params = parse_query_params("content=fts(english).search+term", false).unwrap();
match ¶ms.filters_root[0].op_expr.operation {
Operation::Fts {
op,
language,
value,
} => {
assert_eq!(*op, FtsOperator::Fts);
assert_eq!(language.as_deref(), Some("english"));
assert_eq!(value, "search+term");
}
_ => panic!("Expected FTS operation"),
}
}
#[test]
fn an_empty_columns_parameter_is_a_parse_error() {
let error = parse_columns("").unwrap_err();
assert_eq!(
error.to_string(),
"\"failed to parse columns parameter ()\" (line 1, column 1)"
);
match error {
Error::UnparsableQuery { expected, .. } => assert_eq!(
expected,
"unexpected end of input expecting field name (* or [a..z0..9_$])"
),
other => panic!("wrong variant: {:?}", other),
}
}
#[test]
fn a_missing_field_in_columns_is_reported_where_it_is() {
let error = parse_columns("a,,b").unwrap_err();
assert_eq!(
error.to_string(),
"\"failed to parse columns parameter (a,,b)\" (line 1, column 3)"
);
}
#[test]
fn columns_parses_a_plain_list() {
assert_eq!(
parse_columns("a, b ,c").unwrap(),
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
}
#[test]
fn a_json_path_index_that_is_not_a_number_is_refused() {
assert!(parse_select("data->>--34").is_err());
assert!(parse_select("data->>-34").is_ok());
assert!(parse_select("data->>34").is_ok());
assert!(parse_select("data->>key").is_ok());
}
#[test]
fn an_empty_logic_tree_member_names_the_character_and_the_column() {
let error = parse_logic_param(LogicOperator::Or, false, "()").unwrap_err();
assert_eq!(
error.to_string(),
"\"failed to parse logic tree (())\" (line 1, column 4)"
);
match &error {
Error::UnparsableQuery { expected, .. } => assert_eq!(
expected,
"unexpected \")\" expecting field name (* or [a..z0..9_$]), \
negation operator (not) or logic operator (and, or)"
),
other => panic!("wrong variant: {:?}", other),
}
let error = parse_logic_param(LogicOperator::And, false, "()").unwrap_err();
assert_eq!(
error.to_string(),
"\"failed to parse logic tree (())\" (line 1, column 5)"
);
let error = parse_logic_param(LogicOperator::Or, false, "(,)").unwrap_err();
assert_eq!(
error.to_string(),
"\"failed to parse logic tree ((,))\" (line 1, column 4)"
);
}
}