Skip to main content

postrust_core/api_request/
query_params.rs

1//! Query parameter parsing using nom.
2//!
3//! Parses URL query strings into structured filter, select, order, and range data.
4//! Mirrors PostgREST's QueryParams.hs parsing logic.
5
6use super::types::*;
7use crate::error::{Error, Result};
8use nom::{
9    branch::alt,
10    bytes::complete::{tag, take_while1},
11    character::complete::char,
12    combinator::{opt, value},
13    multi::{many0, separated_list0},
14    sequence::preceded,
15    IResult,
16};
17use percent_encoding::percent_decode_str;
18
19/// Parse a query string into QueryParams.
20///
21/// `is_rpc` changes what an unrecognized key means. On a table, `a=2` is a
22/// malformed filter and an error, because a filter value must carry an
23/// operator. On a function, it is an argument -- so every such key is also
24/// recorded as a candidate argument, and one that fails to parse as a filter
25/// is no longer fatal. Which candidates are really arguments is settled at
26/// plan time, against the parameters the routine actually declares; a
27/// well-formed filter such as `id=eq.1` stays available to filter the result.
28pub fn parse_query_params(query: &str, is_rpc: bool) -> Result<QueryParams> {
29    let mut params = QueryParams::default();
30
31    if query.is_empty() {
32        return Ok(params);
33    }
34
35    // Sort parameters for canonical form
36    let mut pairs: Vec<(&str, &str)> = query
37        .split('&')
38        .filter_map(|pair| {
39            let mut parts = pair.splitn(2, '=');
40            Some((parts.next()?, parts.next().unwrap_or("")))
41        })
42        .collect();
43    pairs.sort_by_key(|(k, _)| *k);
44    params.canonical = pairs
45        .iter()
46        .map(|(k, v)| format!("{}={}", k, v))
47        .collect::<Vec<_>>()
48        .join("&");
49
50    for (key, value) in pairs {
51        let decoded_value = percent_decode_str(value)
52            .decode_utf8()
53            .map_err(|_| Error::InvalidQueryParam(key.into()))?
54            .to_string();
55
56        // The key is decoded as well as the value. A filter on a JSON path
57        // arrives as `data-%3E%3Eb` from any client that escapes `>`, and
58        // would otherwise be looked up as a column of that literal name.
59        let decoded_key = percent_decode_str(key)
60            .decode_utf8()
61            .map_err(|_| Error::InvalidQueryParam(key.into()))?
62            .to_string();
63        let key: &str = &decoded_key;
64
65        // Modifiers come before filters, since both are dotted: `clients.order`
66        // orders an embedded resource while `clients.name` filters one.
67        if let Some((path, modifier)) = parse_modifier_key(key) {
68            match modifier {
69                Modifier::Order => {
70                    let (_, terms) = parse_order_param(&decoded_value)?;
71                    params.order.push((path, terms));
72                }
73                Modifier::Limit => {
74                    let limit: i64 = decoded_value
75                        .parse()
76                        .map_err(|_| Error::InvalidQueryParam("limit".into()))?;
77                    // Caught here rather than left to `LIMIT -1`, which
78                    // PostgreSQL rejects with a message about SQL syntax for
79                    // what is a plainly out-of-range request.
80                    if limit < 0 {
81                        return Err(Error::InvalidRange(
82                            "Limit should be greater than or equal to zero.".into(),
83                        ));
84                    }
85                    params.ranges.entry(path.join(".")).or_default().limit = Some(limit);
86                }
87                Modifier::Offset => {
88                    let offset: i64 = decoded_value
89                        .parse()
90                        .map_err(|_| Error::InvalidQueryParam("offset".into()))?;
91                    let range = params.ranges.entry(path.join(".")).or_default();
92                    range.offset = offset;
93                    range.offset_explicit = true;
94                }
95                Modifier::Logic(op, negated) => {
96                    params
97                        .logic
98                        .push((path, parse_logic_param(op, negated, &decoded_value)?));
99                }
100            }
101            continue;
102        }
103
104        match key {
105            "select" => {
106                params.select = parse_select(&decoded_value)?;
107            }
108            "columns" => {
109                params.columns = Some(parse_columns(&decoded_value)?.into_iter().collect());
110            }
111            "on_conflict" => {
112                params.on_conflict = Some(
113                    decoded_value
114                        .split(',')
115                        .map(|s| s.trim().to_string())
116                        .collect(),
117                );
118            }
119            key if !key.starts_with('_') => {
120                if is_rpc {
121                    params.params.push((key.to_string(), decoded_value.clone()));
122                }
123
124                // Filter parameter
125                let parsed = parse_filter_param(key, &decoded_value);
126                let (path, filter) = match parsed {
127                    Ok(parsed) => parsed,
128                    // On a function, a key that isn't a filter is just an
129                    // argument, which was recorded above.
130                    Err(_) if is_rpc => continue,
131                    Err(e) => return Err(e),
132                };
133
134                if path.is_empty() {
135                    params.filter_fields.insert(filter.field.name.clone());
136                    params.filters_root.push(filter);
137                } else {
138                    params.filters.push((path, filter));
139                }
140            }
141            _ => {
142                // RPC parameters (anything else)
143                params.params.push((key.to_string(), decoded_value));
144            }
145        }
146    }
147
148    Ok(params)
149}
150
151// ============================================================================
152// Select Parsing
153// ============================================================================
154
155/// Parse the `columns` parameter value.
156///
157/// A list of field names and nothing else, so the only thing that can go wrong
158/// is a name that is not there -- `?columns=` names none, and `?columns=a,,b`
159/// leaves a gap in the middle. Both were read as a field whose name is the
160/// empty string, which got as far as the schema cache and came back as
161/// "Could not find the '' column", describing a lookup the client never asked
162/// for rather than the parameter it actually wrote.
163fn parse_columns(input: &str) -> Result<Vec<String>> {
164    const EXPECTED: &str = "expecting field name (* or [a..z0..9_$])";
165
166    let mut columns = Vec::new();
167    let mut at = 0usize;
168
169    for (index, field) in input.split(',').enumerate() {
170        if index > 0 {
171            // The comma that separated this field from the last one.
172            at += 1;
173        }
174        let trimmed = field.trim();
175        if trimmed.is_empty() {
176            return Err(Error::UnparsableQuery {
177                kind: "columns parameter",
178                text: input.to_string(),
179                // Counting from one, at the character that should have begun
180                // the name -- the end of the input where there is none left.
181                column: at + 1,
182                expected: match at >= input.len() {
183                    true => format!("unexpected end of input {}", EXPECTED),
184                    false => format!(
185                        "unexpected {:?} {}",
186                        input[at..].chars().next().unwrap_or_default(),
187                        EXPECTED
188                    ),
189                },
190            });
191        }
192        columns.push(trimmed.to_string());
193        at += field.len();
194    }
195
196    Ok(columns)
197}
198
199/// Parse the `select` parameter value.
200pub fn parse_select(input: &str) -> Result<Vec<SelectItem>> {
201    if input.is_empty() {
202        return Ok(vec![]);
203    }
204
205    // Anything left over is a parse failure, not something to ignore. Left
206    // ignored, one unparsable item silently discarded every item after it --
207    // `select=id, name, billing(address)` returned the id alone.
208    match parse_select_items(input) {
209        Ok(("", items)) => Ok(items),
210        _ => Err(Error::InvalidQueryParam(format!("select={}", input))),
211    }
212}
213
214fn parse_select_items(input: &str) -> IResult<&str, Vec<SelectItem>> {
215    separated_list0(char(','), parse_select_item)(input)
216}
217
218fn parse_select_item(input: &str) -> IResult<&str, SelectItem> {
219    // A space after a comma is ordinary in a hand-written URL, and PostgREST
220    // accepts it.
221    let (input, _) = nom::character::complete::space0(input)?;
222    alt((
223        parse_star,
224        // Before relations: `count()` is spelled exactly like an embed of a
225        // relation named `count` with an empty selection.
226        parse_bare_aggregate,
227        parse_spread_relation,
228        parse_relation_select,
229        parse_field_select,
230    ))(input)
231}
232
233/// Parse `*`, which names every column.
234///
235/// It is a select item like any other, so it composes: `*,clients(id)` asks
236/// for every column of this table and an embed besides, and `clients(*)` asks
237/// for every column of the embedded one.
238fn parse_star(input: &str) -> IResult<&str, SelectItem> {
239    let (input, _) = char('*')(input)?;
240    Ok((input, SelectItem::field("*")))
241}
242
243/// Parse a field-less aggregate: `count()`, `cnt:count()`, `count()::text`.
244///
245/// Only `count` is meaningful without a field -- the others have nothing to
246/// sum -- which is also what keeps this from swallowing embeds.
247fn parse_bare_aggregate(input: &str) -> IResult<&str, SelectItem> {
248    let (input, alias) = opt(parse_alias_prefix)(input)?;
249    let (input, _) = tag("count()")(input)?;
250    let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
251
252    Ok((
253        input,
254        SelectItem::Field {
255            // An empty name marks the `COUNT(*)` form: there is no column to
256            // resolve, and nothing to group by.
257            field: Field::simple(""),
258            aggregate: Some(AggregateFunction::Count),
259            aggregate_cast: cast.map(|s| s.to_string()),
260            cast: None,
261            alias: alias.map(|s| s.to_string()),
262        },
263    ))
264}
265
266/// Parse an aggregate applied to a field: the `.sum()` of `amount.sum()`.
267fn parse_aggregate_suffix(input: &str) -> IResult<&str, AggregateFunction> {
268    let (input, _) = char('.')(input)?;
269    let (input, function) = alt((
270        value(AggregateFunction::Sum, tag("sum")),
271        value(AggregateFunction::Avg, tag("avg")),
272        value(AggregateFunction::Max, tag("max")),
273        value(AggregateFunction::Min, tag("min")),
274        value(AggregateFunction::Count, tag("count")),
275    ))(input)?;
276    let (input, _) = tag("()")(input)?;
277    Ok((input, function))
278}
279
280/// Parse spread relation: `...relation(cols)`
281fn parse_spread_relation(input: &str) -> IResult<&str, SelectItem> {
282    let (input, _) = tag("...")(input)?;
283    let (input, relation) = parse_field_name(input)?;
284    let (input, (hint, join_type)) = parse_relation_modifiers(input)?;
285    // The parentheses are as much a part of the syntax here as they are for a
286    // plain embed. Leaving them for the caller to trip over meant the column
287    // list was silently discarded along with the rest of the select.
288    let (input, _) = char('(')(input)?;
289    let (input, nested) = parse_nested_select(input)?;
290    let (input, _) = char(')')(input)?;
291
292    Ok((
293        input,
294        SelectItem::SpreadRelation {
295            relation,
296            hint,
297            join_type,
298            select: nested,
299        },
300    ))
301}
302
303/// Parse the contents of a relation's parentheses.
304///
305/// Scans to the parenthesis that closes the group -- tracking nesting, so
306/// `books(chapters(id))` is not truncated at the first `)` -- then parses the
307/// contents as a select list.
308fn parse_nested_select(input: &str) -> IResult<&str, Vec<SelectItem>> {
309    let mut depth = 0usize;
310    let mut end = input.len();
311
312    for (idx, ch) in input.char_indices() {
313        match ch {
314            '(' => depth += 1,
315            ')' => {
316                if depth == 0 {
317                    end = idx;
318                    break;
319                }
320                depth -= 1;
321            }
322            _ => {}
323        }
324    }
325
326    let (body, rest) = input.split_at(end);
327
328    if body.is_empty() {
329        return Ok((rest, Vec::new()));
330    }
331
332    match parse_select_items(body) {
333        Ok(("", items)) => Ok((rest, items)),
334        _ => Err(nom::Err::Error(nom::error::Error::new(
335            input,
336            nom::error::ErrorKind::Verify,
337        ))),
338    }
339}
340
341/// Parse relation with embedded select: `relation(select_items)`
342fn parse_relation_select(input: &str) -> IResult<&str, SelectItem> {
343    // As with fields, the alias precedes what it names: `c:clients(*)`.
344    let (input, alias) = opt(parse_alias_prefix)(input)?;
345    let (input, name) = parse_field_name(input)?;
346    let (input, (hint, join_type)) = parse_relation_modifiers(input)?;
347    // The nested selection is parsed rather than skipped: it says which
348    // columns of the related resource to return, and may embed further
349    // relations of its own.
350    let (input, _) = char('(')(input)?;
351    let (input, nested) = parse_nested_select(input)?;
352    let (input, _) = char(')')(input)?;
353
354    Ok((
355        input,
356        SelectItem::Relation {
357            relation: name,
358            alias,
359            hint,
360            join_type,
361            select: nested,
362        },
363    ))
364}
365
366/// Parse field select: `field`, `field::cast`, `field:alias`, `agg(field)`
367fn parse_field_select(input: &str) -> IResult<&str, SelectItem> {
368    // `alias:expression` -- the alias comes first, as in `myId:id` or
369    // `total:sum(amount)`. Only a name immediately followed by `:` is one;
370    // anything else backtracks and is read as the expression itself.
371    let (input, alias) = opt(parse_alias_prefix)(input)?;
372
373    let (input, name) = parse_field_name(input)?;
374    let (input, json_path) = parse_json_path(input)?;
375
376    // A cast on the column binds before the aggregate: `key::integer.sum()`
377    // sums integers, where `.sum()::text` renders the sum as text.
378    let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
379    let (input, aggregate) = opt(parse_aggregate_suffix)(input)?;
380    let (input, aggregate_cast) = if aggregate.is_some() {
381        let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
382        (input, cast.map(|s| s.to_string()))
383    } else {
384        (input, None)
385    };
386
387    Ok((
388        input,
389        SelectItem::Field {
390            field: Field { name, json_path },
391            aggregate,
392            aggregate_cast,
393            cast: cast.map(|s| s.to_string()),
394            alias: alias.map(|s| s.to_string()),
395        },
396    ))
397}
398
399/// Parse the `!`-prefixed modifiers on an embedded relation.
400///
401/// A relation may carry a disambiguating hint, a join type, or both, in either
402/// order: `books!author(*)`, `books!inner(*)`, `books!author!inner(*)`. They
403/// cannot be told apart by position, only by spelling -- `inner` and `left`
404/// are join types and anything else names a foreign key or table -- so each
405/// modifier is read in turn and classified.
406/// An `alias:` prefix, distinguished from the `::` of a cast.
407///
408/// `myId:id` names the column; `id::text` casts it. Both start with an
409/// identifier followed by a colon, so the second colon is what tells them
410/// apart -- without this check `id::text` would be read as an alias `id`
411/// applied to nothing.
412fn parse_alias_prefix(input: &str) -> IResult<&str, String> {
413    let (rest, name) = parse_field_name(input)?;
414    let (rest, _) = char(':')(rest)?;
415
416    if rest.starts_with(':') {
417        return Err(nom::Err::Error(nom::error::Error::new(
418            input,
419            nom::error::ErrorKind::Verify,
420        )));
421    }
422
423    Ok((rest, name))
424}
425
426fn parse_relation_modifiers(input: &str) -> IResult<&str, (Option<String>, Option<JoinType>)> {
427    let (input, modifiers) = many0(preceded(char('!'), parse_identifier))(input)?;
428
429    let mut hint = None;
430    let mut join_type = None;
431    for modifier in modifiers {
432        match modifier {
433            "inner" => join_type = Some(JoinType::Inner),
434            "left" => join_type = Some(JoinType::Left),
435            other => hint = Some(other.to_string()),
436        }
437    }
438
439    Ok((input, (hint, join_type)))
440}
441
442// ============================================================================
443// Filter Parsing
444// ============================================================================
445
446/// Parse a filter parameter (key=value where key is a field name).
447fn parse_filter_param(key: &str, value: &str) -> Result<(EmbedPath, Filter)> {
448    // Parse the key for embedded path: rel.field or field
449    let (path, field) = parse_filter_key(key)?;
450
451    // Parse the value for operator and operand
452    let op_expr = parse_filter_value(value)?;
453
454    Ok((path, Filter::new(field, op_expr)))
455}
456
457/// Split a reference like `data->a->>b` into its column and JSON path.
458///
459/// A name with no arrows is returned unchanged, so this is safe to apply to
460/// every field reference. A trailing fragment that does not parse as a path is
461/// left attached to the column name rather than silently dropped -- the column
462/// then fails to resolve, which is the honest outcome.
463fn split_json_path(reference: &str) -> Field {
464    let Some(arrow) = reference.find("->") else {
465        return Field::simple(reference);
466    };
467
468    let (column, rest) = reference.split_at(arrow);
469    match parse_json_path(rest) {
470        Ok(("", json_path)) if !json_path.is_empty() => Field::with_json_path(column, json_path),
471        _ => Field::simple(reference),
472    }
473}
474
475/// Parse a filter key into the embedded path and the field it names.
476///
477/// The key is a run of field names separated by `.`, optionally followed by a
478/// JSON path: `clients.id`, `data->>a`, `"a.dotted.column"`. Splitting on `.`
479/// alone would cut a quoted name in half, and would take the dot of
480/// `id.something` for a path separator, so the names are parsed rather than
481/// split.
482///
483/// A key the grammar cannot account for falls back to the plain split, which
484/// leaves whatever it produced to fail at column resolution -- an honest
485/// outcome, and the one this had before.
486fn parse_filter_key(key: &str) -> Result<(EmbedPath, Field)> {
487    if let Some(parsed) = parse_field_reference(key) {
488        return Ok(parsed);
489    }
490
491    let mut parts: Vec<&str> = key.split('.').collect();
492    let field = parts
493        .pop()
494        .ok_or_else(|| Error::InvalidQueryParam(key.into()))?;
495    let path = parts.into_iter().map(String::from).collect();
496    Ok((path, split_json_path(field)))
497}
498
499/// A dotted path of field names followed by an optional JSON path.
500///
501/// `None` when the whole key was not consumed, which is the caller's signal
502/// that this grammar does not describe it.
503fn parse_field_reference(key: &str) -> Option<(EmbedPath, Field)> {
504    let mut names = Vec::new();
505    let mut rest = key;
506
507    loop {
508        let (remainder, name) = parse_field_name(rest).ok()?;
509        names.push(name);
510        match remainder.strip_prefix('.') {
511            Some(after) => rest = after,
512            None => {
513                rest = remainder;
514                break;
515            }
516        }
517    }
518
519    let (rest, json_path) = parse_json_path(rest).ok()?;
520    if !rest.is_empty() {
521        return None;
522    }
523
524    let field = names.pop()?;
525    Some((names, Field::with_json_path(field, json_path)))
526}
527
528/// Whether a query parameter reads as a filter rather than a function argument.
529///
530/// On `/rpc/f?id=5&id=gt.2` the same name is both: `5` is the argument the
531/// function takes and `gt.2` filters what it returned. What tells them apart
532/// is the value -- an operator prefix means a filter -- so the classification
533/// has to look at the value rather than the name.
534pub fn value_is_filter(value: &str) -> bool {
535    parse_filter_value(value).is_ok()
536}
537
538/// Parse filter value: `operator.value` or `not.operator.value`
539fn parse_filter_value(value: &str) -> Result<OpExpr> {
540    let whole = value;
541    let (value, negated) = if let Some(rest) = value.strip_prefix("not.") {
542        (rest, true)
543    } else {
544        (value, false)
545    };
546
547    let operation = parse_operation(value).map_err(|error| match error {
548        // Nothing in the value named an operator, so reading stopped at its
549        // first character -- and what a filter may begin with is a short,
550        // published list. Any other failure got further in and has more to say
551        // about where.
552        Error::InvalidQueryParam(_) => Error::UnparsableQuery {
553            kind: "filter",
554            text: whole.to_string(),
555            column: 1,
556            expected: format!(
557                "unexpected \"{}\" expecting \"not\" or operator (eq, gt, ...)",
558                whole.chars().next().unwrap_or_default()
559            ),
560        },
561        other => other,
562    })?;
563    Ok(OpExpr { negated, operation })
564}
565
566/// Map an operator name to its comparison operator, if it is one.
567///
568/// These are exactly the operators that accept an `any`/`all` quantifier.
569fn quant_operator(name: &str) -> Option<QuantOperator> {
570    Some(match name {
571        "eq" => QuantOperator::Equal,
572        "gt" => QuantOperator::GreaterThan,
573        "gte" => QuantOperator::GreaterThanEqual,
574        "lt" => QuantOperator::LessThan,
575        "lte" => QuantOperator::LessThanEqual,
576        "like" => QuantOperator::Like,
577        "ilike" => QuantOperator::ILike,
578        "match" => QuantOperator::Match,
579        "imatch" => QuantOperator::IMatch,
580        _ => return None,
581    })
582}
583
584/// Parse the parenthesised modifier of a quantified comparison.
585fn parse_quantifier(modifier: &str) -> Option<OpQuantifier> {
586    match modifier {
587        "any" => Some(OpQuantifier::Any),
588        "all" => Some(OpQuantifier::All),
589        _ => None,
590    }
591}
592
593/// Parse an operation: `eq.value`, `in.(a,b,c)`, `is.null`, etc.
594fn parse_operation(value: &str) -> Result<Operation> {
595    // A quantified comparison spells the quantifier in parentheses after the
596    // operator name: `col=like(any).{foo,bar}`. Full-text search borrows the
597    // same shape for its language, so the operator name decides the reading.
598    if let Some((name, rest)) = value.split_once('(') {
599        if let Some((modifier, operand)) = rest.split_once(").") {
600            if let (Some(op), Some(quantifier)) = (quant_operator(name), parse_quantifier(modifier))
601            {
602                return Ok(Operation::Quant {
603                    op,
604                    quantifier: Some(quantifier),
605                    value: operand.to_string(),
606                });
607            }
608        }
609    }
610
611    // Comparison operators. Every one of these accepts a quantifier, so they
612    // share the lookup above rather than repeating the name-to-operator map.
613    if let Some((name, rest)) = value.split_once('.') {
614        if let Some(op) = quant_operator(name) {
615            return Ok(Operation::Quant {
616                op,
617                quantifier: None,
618                value: rest.to_string(),
619            });
620        }
621    }
622
623    if let Some(rest) = value.strip_prefix("neq.") {
624        return Ok(Operation::Simple {
625            op: SimpleOperator::NotEqual,
626            value: rest.to_string(),
627        });
628    }
629
630    // Array/Range operators
631    if let Some(rest) = value.strip_prefix("cs.") {
632        return Ok(Operation::Simple {
633            op: SimpleOperator::Contains,
634            value: rest.to_string(),
635        });
636    }
637    if let Some(rest) = value.strip_prefix("cd.") {
638        return Ok(Operation::Simple {
639            op: SimpleOperator::Contained,
640            value: rest.to_string(),
641        });
642    }
643    if let Some(rest) = value.strip_prefix("ov.") {
644        return Ok(Operation::Simple {
645            op: SimpleOperator::Overlap,
646            value: rest.to_string(),
647        });
648    }
649    if let Some(rest) = value.strip_prefix("sl.") {
650        return Ok(Operation::Simple {
651            op: SimpleOperator::StrictlyLeft,
652            value: rest.to_string(),
653        });
654    }
655    if let Some(rest) = value.strip_prefix("sr.") {
656        return Ok(Operation::Simple {
657            op: SimpleOperator::StrictlyRight,
658            value: rest.to_string(),
659        });
660    }
661    if let Some(rest) = value.strip_prefix("nxr.") {
662        return Ok(Operation::Simple {
663            op: SimpleOperator::NotExtendsRight,
664            value: rest.to_string(),
665        });
666    }
667    if let Some(rest) = value.strip_prefix("nxl.") {
668        return Ok(Operation::Simple {
669            op: SimpleOperator::NotExtendsLeft,
670            value: rest.to_string(),
671        });
672    }
673    if let Some(rest) = value.strip_prefix("adj.") {
674        return Ok(Operation::Simple {
675            op: SimpleOperator::Adjacent,
676            value: rest.to_string(),
677        });
678    }
679
680    // IN operator
681    if let Some(rest) = value.strip_prefix("in.") {
682        let values = parse_in_list(rest)?;
683        return Ok(Operation::In(values));
684    }
685
686    // IS operator
687    if let Some(rest) = value.strip_prefix("is.") {
688        // These are the only case-insensitive operands in the grammar, and
689        // `not_null` has no operator of its own -- `is.not_null` is the only
690        // spelling of `IS NOT NULL`.
691        let is_val = match rest.to_ascii_lowercase().as_str() {
692            "null" => IsValue::Null,
693            "not_null" => IsValue::NotNull,
694            "true" => IsValue::True,
695            "false" => IsValue::False,
696            "unknown" => IsValue::Unknown,
697            _ => return Err(unreadable_is_value(rest)),
698        };
699        return Ok(Operation::Is(is_val));
700    }
701
702    // IS DISTINCT FROM
703    if let Some(rest) = value.strip_prefix("isdistinct.") {
704        return Ok(Operation::IsDistinctFrom(rest.to_string()));
705    }
706
707    // Full-text search
708    if let Some(rest) = value.strip_prefix("fts") {
709        return parse_fts(FtsOperator::Fts, rest);
710    }
711    if let Some(rest) = value.strip_prefix("plfts") {
712        return parse_fts(FtsOperator::Plain, rest);
713    }
714    if let Some(rest) = value.strip_prefix("phfts") {
715        return parse_fts(FtsOperator::Phrase, rest);
716    }
717    if let Some(rest) = value.strip_prefix("wfts") {
718        return parse_fts(FtsOperator::Websearch, rest);
719    }
720
721    Err(Error::InvalidQueryParam(value.into()))
722}
723
724/// The four modifiers an order term accepts, and where reading one stopped.
725///
726/// Reading gets as far as the word still could be one of them and reports the
727/// character that ruled the last one out, so `nullslasttt` fails on the tenth
728/// character rather than on the word.
729fn unreadable_order(term: &str, at: usize, part: &str) -> Error {
730    const MODIFIERS: [&str; 4] = ["asc", "desc", "nullsfirst", "nullslast"];
731
732    let read = MODIFIERS
733        .iter()
734        .map(|modifier| {
735            part.chars()
736                .zip(modifier.chars())
737                .take_while(|(a, b)| a == b)
738                .count()
739        })
740        .max()
741        .unwrap_or(0);
742
743    Error::UnparsableQuery {
744        kind: "order",
745        text: term.to_string(),
746        column: at + read + 1,
747        expected: format!(
748            "unexpected {:?} expecting \",\" or end of input",
749            part.chars().nth(read).unwrap_or_default()
750        ),
751    }
752}
753
754/// The five things `is.` accepts, and where reading one stopped.
755///
756/// Reading gets as far as the operand still could be one of them and reports
757/// the character that ruled the last one out -- `is.nil` fails on the `i`,
758/// having read `n` as the start of `null`, while `is.ok` fails on the `o`.
759/// That is where a client has to look, and it is the difference between a
760/// message about a typo and one about the whole request.
761fn unreadable_is_value(rest: &str) -> Error {
762    const VALUES: [&str; 5] = ["null", "not_null", "true", "false", "unknown"];
763
764    let lowered = rest.to_ascii_lowercase();
765    let read = VALUES
766        .iter()
767        .map(|value| {
768            lowered
769                .chars()
770                .zip(value.chars())
771                .take_while(|(a, b)| a == b)
772                .count()
773        })
774        .max()
775        .unwrap_or(0);
776
777    Error::UnparsableQuery {
778        kind: "filter",
779        text: format!("is.{}", rest),
780        // Counting from one, past the `is.` that was read before this.
781        column: 4 + read,
782        expected: format!(
783            "unexpected \"{}\" expecting isVal: ({})",
784            rest.chars().nth(read).unwrap_or_default(),
785            VALUES.join(", ")
786        ),
787    }
788}
789
790/// Parse IN list: `(a,b,c)` -> vec!["a", "b", "c"]
791fn parse_in_list(value: &str) -> Result<Vec<String>> {
792    // Whitespace immediately inside the parentheses is not part of the first
793    // or last element: `in.(    )` is the empty list, while `in.( ,3,4)` has
794    // an empty first element and is a type error the database reports.
795    let inner = value
796        .trim()
797        .strip_prefix('(')
798        .and_then(|s| s.strip_suffix(')'))
799        .map(|inner| {
800            inner
801                .trim_start_matches([' ', '\t'])
802                .trim_end_matches([' ', '\t'])
803        })
804        .ok_or_else(|| Error::InvalidQueryParam(format!("in.{}", value)))?;
805
806    // `in.()` names no values at all, which is not the same as naming one
807    // empty value -- and is what `?id=in.()` returning nothing means.
808    if inner.is_empty() {
809        return Ok(Vec::new());
810    }
811
812    let mut values = Vec::new();
813    let mut rest = inner;
814
815    loop {
816        // Quoting is what lets a value contain the comma that would otherwise
817        // end it, so it counts only when it spans the whole element: in
818        // `Double"Quote"McGraw"` the quotes are part of the name, and taking
819        // them for syntax would silently change what was asked for.
820        let element = match quoted_element(rest) {
821            Some((unquoted, remainder)) => {
822                rest = remainder;
823                unquoted
824            }
825            None => {
826                let end = rest.find(',').unwrap_or(rest.len());
827                let (element, remainder) = rest.split_at(end);
828                rest = remainder;
829                element.to_string()
830            }
831        };
832        values.push(element);
833
834        match rest.strip_prefix(',') {
835            Some(remainder) => rest = remainder,
836            None => break,
837        }
838    }
839
840    Ok(values)
841}
842
843/// A complete double-quoted element at the head of `input`.
844///
845/// `None` unless the closing quote ends the element -- what follows it has to
846/// be the comma that separates elements, or nothing at all.
847fn quoted_element(input: &str) -> Option<(String, &str)> {
848    let body = input.strip_prefix('"')?;
849
850    let mut value = String::new();
851    let mut chars = body.char_indices();
852    while let Some((index, ch)) = chars.next() {
853        match ch {
854            '\\' => {
855                let (_, escaped) = chars.next()?;
856                value.push(escaped);
857            }
858            '"' => {
859                let rest = &body[index + 1..];
860                return match rest.is_empty() || rest.starts_with(',') {
861                    true => Some((value, rest)),
862                    false => None,
863                };
864            }
865            other => value.push(other),
866        }
867    }
868
869    None
870}
871
872/// Parse FTS operation: `(language).query` or `.query`
873fn parse_fts(op: FtsOperator, rest: &str) -> Result<Operation> {
874    if let Some(rest) = rest.strip_prefix('(') {
875        // Has language specifier
876        let (lang, query) = rest
877            .split_once(").")
878            .ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
879        return Ok(Operation::Fts {
880            op,
881            language: Some(lang.to_string()),
882            value: query.to_string(),
883        });
884    }
885
886    let query = rest
887        .strip_prefix('.')
888        .ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
889    Ok(Operation::Fts {
890        op,
891        language: None,
892        value: query.to_string(),
893    })
894}
895
896// ============================================================================
897// Order Parsing
898// ============================================================================
899
900/// Parse order parameter: `col.desc.nullsfirst,col2.asc`
901fn parse_order_param(value: &str) -> Result<(EmbedPath, Vec<OrderTerm>)> {
902    let terms: Vec<OrderTerm> = value
903        .split(',')
904        .map(|s| parse_order_term(s.trim()))
905        .collect::<Result<Vec<_>>>()?;
906    Ok((vec![], terms))
907}
908
909fn parse_order_term(value: &str) -> Result<OrderTerm> {
910    let parts: Vec<&str> = value.split('.').collect();
911    if parts.is_empty() {
912        return Err(Error::InvalidQueryParam("order".into()));
913    }
914
915    let field_name = parts[0];
916    let field = split_json_path(field_name);
917    let mut direction = None;
918    let mut nulls = None;
919
920    // Where each modifier starts within the term, so a word that is not one
921    // can be reported at the character that made it not one.
922    let mut at = field_name.len() + 1;
923    for part in &parts[1..] {
924        match *part {
925            "asc" => direction = Some(OrderDirection::Asc),
926            "desc" => direction = Some(OrderDirection::Desc),
927            "nullsfirst" => nulls = Some(OrderNulls::First),
928            "nullslast" => nulls = Some(OrderNulls::Last),
929            // Not one of the four. Ignoring it answered a request nobody made:
930            // `order=id.asc.nullslasttt` was read as `id.asc` and the typo
931            // silently changed nothing, so a client asking for nulls last got
932            // whatever the table happened to give it.
933            other => return Err(unreadable_order(value, at, other)),
934        }
935        at += part.len() + 1;
936    }
937
938    // `clients(name)` orders by a column of an embedded resource rather than
939    // by one of this table's own.
940    if let Some((relation, rest)) = field_name.split_once('(') {
941        if let Some(column) = rest.strip_suffix(')') {
942            return Ok(OrderTerm::Relation {
943                relation: relation.to_string(),
944                field: split_json_path(column),
945                direction,
946                nulls,
947            });
948        }
949    }
950
951    Ok(OrderTerm::Field {
952        field,
953        direction,
954        nulls,
955    })
956}
957
958// ============================================================================
959// Logic Tree Parsing
960// ============================================================================
961
962/// Parse `and` or `or` parameter: `(filter1,filter2)`
963fn parse_logic_param(op: LogicOperator, negated: bool, value: &str) -> Result<LogicTree> {
964    // Whitespace around the group and around each member is insignificant, as
965    // it is in PostgREST: `and=( a.eq.1 , b.eq.2 )` is the same request as
966    // `and=(a.eq.1,b.eq.2)`.
967    // The group ends at the parenthesis that closes it, not at the last one in
968    // the string: `and=(a.eq.1,b.eq.2))` is a well-formed group with a stray
969    // character after it, and PostgREST reads it as one.
970    let trimmed = value.trim();
971    let inner = balanced_group(trimmed).ok_or_else(|| Error::InvalidQueryParam(value.into()))?;
972
973    // Where a member is empty there is nothing to hand the filter parser, and
974    // "Invalid request" says only that something in a URL the client wrote is
975    // wrong. PostgREST names the character and what would have been accepted
976    // there, counting from the start of the tree as it spells it -- `or=(...)`
977    // is read as `or(...)`, so the `=` is not a column and the operator's own
978    // name is.
979    let members = split_top_level(inner);
980    for member in &members {
981        if !member.trim().is_empty() {
982            continue;
983        }
984        let at = offset_within(trimmed, member);
985        return Err(Error::UnparsableQuery {
986            kind: "logic tree",
987            text: trimmed.to_string(),
988            column: negated as usize * "not.".len()
989                + match op {
990                    LogicOperator::And => 3,
991                    LogicOperator::Or => 2,
992                }
993                + at
994                + 1,
995            expected: format!(
996                "unexpected \"{}\" expecting field name (* or [a..z0..9_$]), \
997                 negation operator (not) or logic operator (and, or)",
998                trimmed[at..].chars().next().unwrap_or_default()
999            ),
1000        });
1001    }
1002
1003    Ok(LogicTree::Expr {
1004        negated,
1005        op,
1006        children: members
1007            .into_iter()
1008            .map(parse_logic_child)
1009            .collect::<Result<Vec<_>>>()?,
1010    })
1011}
1012
1013/// Where `part` begins inside `whole`, which it is a subslice of.
1014///
1015/// The split loses the positions and the message needs them; taking them from
1016/// the pointers is exact where searching for the text would find the wrong
1017/// occurrence of a repeated member.
1018fn offset_within(whole: &str, part: &str) -> usize {
1019    (part.as_ptr() as usize).saturating_sub(whole.as_ptr() as usize)
1020}
1021
1022/// The contents of a parenthesised group at the head of `input`.
1023///
1024/// Anything after the closing parenthesis is not part of the group and is
1025/// discarded, which is what a parser that does not demand end-of-input does.
1026fn balanced_group(input: &str) -> Option<&str> {
1027    let body = input.strip_prefix('(')?;
1028
1029    let mut depth = 0usize;
1030    let mut quoted = false;
1031    let mut escaped = false;
1032
1033    for (idx, ch) in body.char_indices() {
1034        if quoted {
1035            match ch {
1036                _ if escaped => escaped = false,
1037                '\\' => escaped = true,
1038                '"' => quoted = false,
1039                _ => {}
1040            }
1041            continue;
1042        }
1043        match ch {
1044            '"' => quoted = true,
1045            '(' => depth += 1,
1046            ')' if depth == 0 => return Some(&body[..idx]),
1047            ')' => depth -= 1,
1048            _ => {}
1049        }
1050    }
1051
1052    None
1053}
1054
1055/// Split a comma-separated list on its top-level commas only.
1056///
1057/// A logic list holds whole conditions, and a condition may contain commas of
1058/// its own -- `id.in.(1,2)`, `arr.cs.{1,2}`, a nested `and(...)`, or a quoted
1059/// value. Splitting on every comma tears those apart.
1060fn split_top_level(input: &str) -> Vec<&str> {
1061    let mut parts = Vec::new();
1062    let mut depth = 0usize;
1063    let mut quoted = false;
1064    let mut escaped = false;
1065    let mut start = 0usize;
1066
1067    for (idx, ch) in input.char_indices() {
1068        if quoted {
1069            match ch {
1070                _ if escaped => escaped = false,
1071                '\\' => escaped = true,
1072                '"' => quoted = false,
1073                _ => {}
1074            }
1075            continue;
1076        }
1077        match ch {
1078            '"' => quoted = true,
1079            '(' | '{' | '[' => depth += 1,
1080            ')' | '}' | ']' => depth = depth.saturating_sub(1),
1081            ',' if depth == 0 => {
1082                parts.push(&input[start..idx]);
1083                start = idx + ch.len_utf8();
1084            }
1085            _ => {}
1086        }
1087    }
1088    parts.push(&input[start..]);
1089    parts
1090}
1091
1092/// Parse one member of a logic list: either a nested group or a condition.
1093fn parse_logic_child(item: &str) -> Result<LogicTree> {
1094    let item = item.trim();
1095
1096    // `not.and(...)` negates the group. A leading `not.` on anything else
1097    // belongs to the condition's operator (`arr.not.cs.{1,2}`), so it is left
1098    // in place for the filter parser to deal with.
1099    let (body, negated) = match item.strip_prefix("not.") {
1100        Some(rest) if rest.starts_with("and(") || rest.starts_with("or(") => (rest, true),
1101        _ => (item, false),
1102    };
1103
1104    for (name, op) in [("and", LogicOperator::And), ("or", LogicOperator::Or)] {
1105        if let Some(rest) = body.strip_prefix(name) {
1106            let rest = rest.trim_start();
1107            if rest.starts_with('(') && rest.ends_with(')') {
1108                return parse_logic_param(op, negated, rest);
1109            }
1110        }
1111    }
1112
1113    let (key, val) = body
1114        .split_once('.')
1115        .ok_or_else(|| Error::InvalidQueryParam(item.into()))?;
1116    let (_, filter) = parse_filter_param(key, &unquote_logic_operand(val))?;
1117    Ok(LogicTree::Stmt(filter))
1118}
1119
1120/// Strip the quotes from a logic-tree operand.
1121///
1122/// Inside `or=(...)` a value may be quoted so that it can contain the comma
1123/// that would otherwise end it -- `name.eq."(grandchild,entity,4)"` -- and the
1124/// quotes are then syntax rather than part of the value. Outside a logic tree
1125/// there is nothing to protect the value from, so a quote there is a
1126/// character like any other and is left alone.
1127fn unquote_logic_operand(value: &str) -> String {
1128    let Some(open) = value.find('"') else {
1129        return value.to_string();
1130    };
1131    // The quote has to open the operand rather than sit inside one: `id.in.(
1132    // "a","b")` is a list, which does its own unquoting element by element.
1133    if open != 0 && !value[..open].ends_with('.') {
1134        return value.to_string();
1135    }
1136    if !value.ends_with('"') || value.len() < open + 2 {
1137        return value.to_string();
1138    }
1139
1140    let mut unquoted = String::from(&value[..open]);
1141    let mut chars = value[open + 1..value.len() - 1].chars();
1142    while let Some(ch) = chars.next() {
1143        match ch {
1144            '\\' => {
1145                if let Some(escaped) = chars.next() {
1146                    unquoted.push(escaped);
1147                }
1148            }
1149            other => unquoted.push(other),
1150        }
1151    }
1152    unquoted
1153}
1154
1155/// A query parameter that modifies a resource rather than filtering it.
1156enum Modifier {
1157    Order,
1158    Limit,
1159    Offset,
1160    Logic(LogicOperator, bool),
1161}
1162
1163/// Recognise a modifier key and the embedded resource it addresses.
1164///
1165/// Every modifier may be aimed at an embedded resource by prefixing it with
1166/// the path to that resource: `clients.order=name` orders the embedded
1167/// clients, `clients.limit=1` pages them, `clients.or=(...)` filters them. The
1168/// bare forms are the same thing with an empty path.
1169fn parse_modifier_key(key: &str) -> Option<(EmbedPath, Modifier)> {
1170    let mut parts: Vec<&str> = key.split('.').collect();
1171    let last = parts.pop()?;
1172
1173    let modifier = match last {
1174        "order" => Modifier::Order,
1175        "limit" => Modifier::Limit,
1176        "offset" => Modifier::Offset,
1177        "and" | "or" => {
1178            let op = if last == "and" {
1179                LogicOperator::And
1180            } else {
1181                LogicOperator::Or
1182            };
1183            // `not.or=(...)` negates the whole group.
1184            let negated = parts.last() == Some(&"not");
1185            if negated {
1186                parts.pop();
1187            }
1188            Modifier::Logic(op, negated)
1189        }
1190        _ => return None,
1191    };
1192
1193    Some((parts.into_iter().map(String::from).collect(), modifier))
1194}
1195
1196// ============================================================================
1197// Helper Parsers
1198// ============================================================================
1199
1200/// The characters a bare identifier is made of.
1201///
1202/// PostgREST's set exactly: letters, digits, `_`, `$` and the space, with the
1203/// result trimmed. A space is allowed because a column may genuinely have one
1204/// -- `?select=Just A Server Model` -- and trimming is what keeps
1205/// `?select=id, name` from asking for a column called `name ` .
1206fn parse_identifier(input: &str) -> IResult<&str, &str> {
1207    let (rest, matched) =
1208        take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '$' || c == ' ')(input)?;
1209    Ok((rest, matched.trim()))
1210}
1211
1212/// Parse a field name: a quoted name, or dash-joined identifiers.
1213///
1214/// Quoting is how a client names a column the bare grammar cannot spell --
1215/// `"a.dotted.column"`, `"(inside,parens)"` -- and a backslash escapes the
1216/// next character inside it.
1217///
1218/// Unquoted, a `-` joins identifiers into one name, so `field-with_sep` is a
1219/// single column. A `-` that starts `->` is not a join: that is a JSON path,
1220/// and it belongs to whatever comes after the name.
1221fn parse_field_name(input: &str) -> IResult<&str, String> {
1222    if let Ok(quoted) = parse_quoted_name(input) {
1223        return Ok(quoted);
1224    }
1225
1226    let (mut rest, first) = parse_identifier(input)?;
1227    let mut name = first.to_string();
1228
1229    while let Some(after_dash) = rest.strip_prefix('-') {
1230        if after_dash.starts_with('>') {
1231            break;
1232        }
1233        let Ok((next, segment)) = parse_identifier(after_dash) else {
1234            break;
1235        };
1236        name.push('-');
1237        name.push_str(segment);
1238        rest = next;
1239    }
1240
1241    Ok((rest, name))
1242}
1243
1244/// Parse a double-quoted name, honouring backslash escapes.
1245fn parse_quoted_name(input: &str) -> IResult<&str, String> {
1246    let (rest, _) = char('"')(input)?;
1247
1248    let mut name = String::new();
1249    let mut chars = rest.char_indices();
1250    while let Some((index, ch)) = chars.next() {
1251        match ch {
1252            '\\' => {
1253                if let Some((_, escaped)) = chars.next() {
1254                    name.push(escaped);
1255                }
1256            }
1257            '"' => return Ok((&rest[index + 1..], name)),
1258            other => name.push(other),
1259        }
1260    }
1261
1262    Err(nom::Err::Error(nom::error::Error::new(
1263        input,
1264        nom::error::ErrorKind::Verify,
1265    )))
1266}
1267
1268fn parse_json_path(input: &str) -> IResult<&str, JsonPath> {
1269    // `->>` first: it starts with `->`, so trying the shorter one first would
1270    // match it and leave the second `>` at the head of the key.
1271    many0(alt((parse_double_arrow, parse_arrow)))(input)
1272}
1273
1274/// Parse one step of a JSON path.
1275///
1276/// A JSON object key can be very nearly anything, and PostgREST's grammar says
1277/// so: everything up to the next thing the select grammar itself reserves is
1278/// the key. `data->>!@#$%^&*_e` and `data->23-xy-45` are ordinary keys, not
1279/// syntax errors, and reading a key as alphanumeric-only silently truncated
1280/// the path and returned the whole column instead.
1281///
1282/// A key made entirely of digits is an array index, which is why this decides
1283/// between the two only once the whole key has been read: `data->0xy1` steps
1284/// into the key `0xy1`, not into element 0 of an array.
1285fn parse_json_operand(input: &str) -> IResult<&str, JsonOperand> {
1286    let mut end = input.len();
1287    for (idx, ch) in input.char_indices() {
1288        // `,` ends the select item, `(` and `)` bound an embedding, `:` starts
1289        // a cast or an alias, and `->` starts the next step.
1290        if matches!(ch, ',' | '(' | ')' | ':') || input[idx..].starts_with("->") {
1291            end = idx;
1292            break;
1293        }
1294    }
1295
1296    let (key, rest) = input.split_at(end);
1297    if key.is_empty() {
1298        return Err(nom::Err::Error(nom::error::Error::new(
1299            input,
1300            nom::error::ErrorKind::TakeWhile1,
1301        )));
1302    }
1303
1304    let operand = match key.parse::<i32>() {
1305        Ok(index) => JsonOperand::Idx(index),
1306        // A leading `-` begins an array index counted from the end, so what
1307        // follows it has to be a number. `data->>--34` was read as a key
1308        // literally named `--34` and answered 200 with nulls, where the
1309        // grammar has no such key in it and PostgREST says so.
1310        // A `Failure` rather than an `Error`: the arrow has already been read,
1311        // so this is not a step that some other rule might match instead.
1312        // Reported as a recoverable error, `alt` backtracked from `->>` to
1313        // `->` and read the rest as a key beginning with `>`, which turned a
1314        // malformed index into an ordinary lookup that answered 200.
1315        Err(_) if key.starts_with('-') => {
1316            return Err(nom::Err::Failure(nom::error::Error::new(
1317                &input[1..],
1318                nom::error::ErrorKind::Digit,
1319            )))
1320        }
1321        Err(_) => JsonOperand::Key(key.to_string()),
1322    };
1323    Ok((rest, operand))
1324}
1325
1326fn parse_arrow(input: &str) -> IResult<&str, JsonOperation> {
1327    let (input, _) = tag("->")(input)?;
1328    let (input, operand) = parse_json_operand(input)?;
1329    Ok((input, JsonOperation::Arrow(operand)))
1330}
1331
1332fn parse_double_arrow(input: &str) -> IResult<&str, JsonOperation> {
1333    let (input, _) = tag("->>")(input)?;
1334    let (input, operand) = parse_json_operand(input)?;
1335    Ok((input, JsonOperation::DoubleArrow(operand)))
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340    use super::*;
1341
1342    #[test]
1343    fn test_parse_simple_filter() {
1344        let params = parse_query_params("name=eq.John", false).unwrap();
1345        assert_eq!(params.filters_root.len(), 1);
1346        assert_eq!(params.filters_root[0].field.name, "name");
1347    }
1348
1349    #[test]
1350    fn test_parse_negated_filter() {
1351        let params = parse_query_params("status=not.eq.active", false).unwrap();
1352        assert!(params.filters_root[0].op_expr.negated);
1353    }
1354
1355    #[test]
1356    fn test_parse_in_filter() {
1357        let params = parse_query_params("id=in.(1,2,3)", false).unwrap();
1358        match &params.filters_root[0].op_expr.operation {
1359            Operation::In(values) => {
1360                assert_eq!(values, &vec!["1", "2", "3"]);
1361            }
1362            _ => panic!("Expected In operation"),
1363        }
1364    }
1365
1366    #[test]
1367    fn test_json_path_double_arrow_wins_over_single() {
1368        let items = parse_select("settings->foo->>bar").unwrap();
1369        match &items[0] {
1370            SelectItem::Field { field, .. } => {
1371                assert_eq!(
1372                    field.json_path,
1373                    vec![
1374                        JsonOperation::Arrow(JsonOperand::Key("foo".into())),
1375                        JsonOperation::DoubleArrow(JsonOperand::Key("bar".into())),
1376                    ]
1377                );
1378            }
1379            other => panic!("expected a field, got {:?}", other),
1380        }
1381    }
1382
1383    #[test]
1384    fn test_json_path_accepts_awkward_keys() {
1385        // PostgREST reserves only what its own grammar needs, so a key may
1386        // hold punctuation and hyphens.
1387        let items = parse_select("data->23-xy-45->>!@#$%^&*_e").unwrap();
1388        match &items[0] {
1389            SelectItem::Field { field, .. } => {
1390                assert_eq!(
1391                    field.json_path,
1392                    vec![
1393                        JsonOperation::Arrow(JsonOperand::Key("23-xy-45".into())),
1394                        JsonOperation::DoubleArrow(JsonOperand::Key("!@#$%^&*_e".into())),
1395                    ]
1396                );
1397            }
1398            other => panic!("expected a field, got {:?}", other),
1399        }
1400    }
1401
1402    #[test]
1403    fn test_json_path_digits_are_an_index_but_only_when_whole() {
1404        let items = parse_select("data->0,other->0xy1").unwrap();
1405        let paths: Vec<_> = items
1406            .iter()
1407            .map(|i| match i {
1408                SelectItem::Field { field, .. } => field.json_path.clone(),
1409                other => panic!("expected a field, got {:?}", other),
1410            })
1411            .collect();
1412        assert_eq!(paths[0], vec![JsonOperation::Arrow(JsonOperand::Idx(0))]);
1413        assert_eq!(
1414            paths[1],
1415            vec![JsonOperation::Arrow(JsonOperand::Key("0xy1".into()))]
1416        );
1417    }
1418
1419    #[test]
1420    fn test_json_path_stops_at_a_cast() {
1421        let items = parse_select("settings->>foo::json").unwrap();
1422        match &items[0] {
1423            SelectItem::Field { field, cast, .. } => {
1424                assert_eq!(
1425                    field.json_path,
1426                    vec![JsonOperation::DoubleArrow(JsonOperand::Key("foo".into()))]
1427                );
1428                assert_eq!(cast.as_deref(), Some("json"));
1429            }
1430            other => panic!("expected a field, got {:?}", other),
1431        }
1432    }
1433
1434    #[test]
1435    fn test_parse_spread_relation_keeps_its_columns() {
1436        let items = parse_select("title,...directors(first_name,last_name)").unwrap();
1437        assert_eq!(items.len(), 2);
1438        match &items[1] {
1439            SelectItem::SpreadRelation {
1440                relation, select, ..
1441            } => {
1442                assert_eq!(relation, "directors");
1443                let names: Vec<_> = select
1444                    .iter()
1445                    .map(|item| match item {
1446                        SelectItem::Field { field, .. } => field.name.clone(),
1447                        other => panic!("expected a field, got {:?}", other),
1448                    })
1449                    .collect();
1450                assert_eq!(names, vec!["first_name", "last_name"]);
1451            }
1452            other => panic!("expected a spread relation, got {:?}", other),
1453        }
1454    }
1455
1456    #[test]
1457    fn test_parse_spread_relation_does_not_swallow_later_items() {
1458        // The column list used to be left unconsumed, which silently truncated
1459        // everything after it.
1460        let items = parse_select("...directors(name),year").unwrap();
1461        assert_eq!(items.len(), 2);
1462        assert!(matches!(items[0], SelectItem::SpreadRelation { .. }));
1463        match &items[1] {
1464            SelectItem::Field { field, .. } => assert_eq!(field.name, "year"),
1465            other => panic!("expected a field, got {:?}", other),
1466        }
1467    }
1468
1469    #[test]
1470    fn test_parse_nested_spread_relation() {
1471        let items = parse_select("id,films(title,...directors(name))").unwrap();
1472        match &items[1] {
1473            SelectItem::Relation { select, .. } => {
1474                assert_eq!(select.len(), 2);
1475                assert!(matches!(select[1], SelectItem::SpreadRelation { .. }));
1476            }
1477            other => panic!("expected a relation, got {:?}", other),
1478        }
1479    }
1480
1481    #[test]
1482    fn test_parse_in_filter_with_quoted_commas() {
1483        let params = parse_query_params(r#"name=in.("hi,there","yes,you")"#, false).unwrap();
1484        match &params.filters_root[0].op_expr.operation {
1485            Operation::In(values) => {
1486                assert_eq!(values, &vec!["hi,there".to_string(), "yes,you".to_string()]);
1487            }
1488            other => panic!("expected an In operation, got {:?}", other),
1489        }
1490    }
1491
1492    #[test]
1493    fn test_parse_in_filter_with_escapes() {
1494        let params = parse_query_params(r#"name=in.("a\"b","c\\d")"#, false).unwrap();
1495        match &params.filters_root[0].op_expr.operation {
1496            Operation::In(values) => {
1497                assert_eq!(values, &vec![r#"a"b"#.to_string(), r#"c\d"#.to_string()]);
1498            }
1499            other => panic!("expected an In operation, got {:?}", other),
1500        }
1501    }
1502
1503    #[test]
1504    fn test_parse_quantified_filter() {
1505        for (query, expected) in [
1506            ("id=eq(any).{1,2,3}", OpQuantifier::Any),
1507            ("id=eq(all).{1,2,3}", OpQuantifier::All),
1508        ] {
1509            let params = parse_query_params(query, false).unwrap();
1510            match &params.filters_root[0].op_expr.operation {
1511                Operation::Quant {
1512                    op,
1513                    quantifier,
1514                    value,
1515                } => {
1516                    assert_eq!(op, &QuantOperator::Equal);
1517                    assert_eq!(quantifier.as_ref(), Some(&expected));
1518                    assert_eq!(value, "{1,2,3}");
1519                }
1520                other => panic!("expected a quantified operation, got {:?}", other),
1521            }
1522        }
1523    }
1524
1525    #[test]
1526    fn test_parse_quantified_like_keeps_array_literal() {
1527        let params = parse_query_params("name=like(any).{foo*,bar*}", false).unwrap();
1528        match &params.filters_root[0].op_expr.operation {
1529            Operation::Quant {
1530                op,
1531                quantifier,
1532                value,
1533            } => {
1534                assert_eq!(op, &QuantOperator::Like);
1535                assert_eq!(quantifier.as_ref(), Some(&OpQuantifier::Any));
1536                // The `*`-to-`%` mapping belongs to SQL generation, so the
1537                // parsed operand is still the literal the client sent.
1538                assert_eq!(value, "{foo*,bar*}");
1539            }
1540            other => panic!("expected a quantified operation, got {:?}", other),
1541        }
1542    }
1543
1544    #[test]
1545    fn test_parse_negated_quantified_filter() {
1546        let params = parse_query_params("id=not.eq(any).{1,2}", false).unwrap();
1547        assert!(params.filters_root[0].op_expr.negated);
1548        assert!(matches!(
1549            params.filters_root[0].op_expr.operation,
1550            Operation::Quant {
1551                quantifier: Some(OpQuantifier::Any),
1552                ..
1553            }
1554        ));
1555    }
1556
1557    #[test]
1558    fn test_quantifier_form_does_not_capture_fts_language() {
1559        // `fts(english).x` has the same shape but is not a quantified
1560        // comparison; the operator name is what tells the two apart.
1561        let params = parse_query_params("body=fts(english).cat", false).unwrap();
1562        match &params.filters_root[0].op_expr.operation {
1563            Operation::Fts { language, .. } => {
1564                assert_eq!(language.as_deref(), Some("english"));
1565            }
1566            other => panic!("expected an fts operation, got {:?}", other),
1567        }
1568    }
1569
1570    #[test]
1571    fn test_parse_is_null() {
1572        let params = parse_query_params("deleted_at=is.null", false).unwrap();
1573        match &params.filters_root[0].op_expr.operation {
1574            Operation::Is(IsValue::Null) => {}
1575            _ => panic!("Expected Is Null"),
1576        }
1577    }
1578
1579    #[test]
1580    fn test_parse_order() {
1581        let params = parse_query_params("order=name.asc,age.desc.nullslast", false).unwrap();
1582        assert_eq!(params.order.len(), 1);
1583        let (_, terms) = &params.order[0];
1584        assert_eq!(terms.len(), 2);
1585    }
1586
1587    #[test]
1588    fn test_parse_limit_offset() {
1589        let params = parse_query_params("limit=10&offset=20", false).unwrap();
1590        let range = params.ranges.get("").unwrap();
1591        assert_eq!(range.limit, Some(10));
1592        assert_eq!(range.offset, 20);
1593    }
1594
1595    #[test]
1596    fn test_parse_select() {
1597        let items = parse_select("id,name,orders(id,amount)").unwrap();
1598        assert_eq!(items.len(), 3);
1599    }
1600
1601    #[test]
1602    fn test_parse_fts() {
1603        let params = parse_query_params("content=fts(english).search+term", false).unwrap();
1604        match &params.filters_root[0].op_expr.operation {
1605            Operation::Fts {
1606                op,
1607                language,
1608                value,
1609            } => {
1610                assert_eq!(*op, FtsOperator::Fts);
1611                assert_eq!(language.as_deref(), Some("english"));
1612                assert_eq!(value, "search+term");
1613            }
1614            _ => panic!("Expected FTS operation"),
1615        }
1616    }
1617    /// `?columns=` names no field, and is reported as the parameter it is.
1618    #[test]
1619    fn an_empty_columns_parameter_is_a_parse_error() {
1620        let error = parse_columns("").unwrap_err();
1621        assert_eq!(
1622            error.to_string(),
1623            "\"failed to parse columns parameter ()\" (line 1, column 1)"
1624        );
1625        match error {
1626            Error::UnparsableQuery { expected, .. } => assert_eq!(
1627                expected,
1628                "unexpected end of input expecting field name (* or [a..z0..9_$])"
1629            ),
1630            other => panic!("wrong variant: {:?}", other),
1631        }
1632    }
1633
1634    /// A gap in the middle is reported where the gap is, not at the end.
1635    #[test]
1636    fn a_missing_field_in_columns_is_reported_where_it_is() {
1637        let error = parse_columns("a,,b").unwrap_err();
1638        assert_eq!(
1639            error.to_string(),
1640            "\"failed to parse columns parameter (a,,b)\" (line 1, column 3)"
1641        );
1642    }
1643
1644    #[test]
1645    fn columns_parses_a_plain_list() {
1646        assert_eq!(
1647            parse_columns("a, b ,c").unwrap(),
1648            vec!["a".to_string(), "b".to_string(), "c".to_string()]
1649        );
1650    }
1651
1652    /// A `-` inside a JSON path begins an index, so a non-number after it is
1653    /// not a key by another name.
1654    #[test]
1655    fn a_json_path_index_that_is_not_a_number_is_refused() {
1656        assert!(parse_select("data->>--34").is_err());
1657        assert!(parse_select("data->>-34").is_ok());
1658        assert!(parse_select("data->>34").is_ok());
1659        assert!(parse_select("data->>key").is_ok());
1660    }
1661    /// An empty member of a logic tree is reported the way PostgREST reports
1662    /// it, which counts from the tree as it spells it: `or=(...)` is read as
1663    /// `or(...)`, so the `=` is not a column and the operator's name is.
1664    #[test]
1665    fn an_empty_logic_tree_member_names_the_character_and_the_column() {
1666        let error = parse_logic_param(LogicOperator::Or, false, "()").unwrap_err();
1667        assert_eq!(
1668            error.to_string(),
1669            "\"failed to parse logic tree (())\" (line 1, column 4)"
1670        );
1671        match &error {
1672            Error::UnparsableQuery { expected, .. } => assert_eq!(
1673                expected,
1674                "unexpected \")\" expecting field name (* or [a..z0..9_$]), \
1675                 negation operator (not) or logic operator (and, or)"
1676            ),
1677            other => panic!("wrong variant: {:?}", other),
1678        }
1679
1680        // `and` is a longer name, so the same shape reports one column later.
1681        let error = parse_logic_param(LogicOperator::And, false, "()").unwrap_err();
1682        assert_eq!(
1683            error.to_string(),
1684            "\"failed to parse logic tree (())\" (line 1, column 5)"
1685        );
1686
1687        // A gap between two members is reported where the gap is.
1688        let error = parse_logic_param(LogicOperator::Or, false, "(,)").unwrap_err();
1689        assert_eq!(
1690            error.to_string(),
1691            "\"failed to parse logic tree ((,))\" (line 1, column 4)"
1692        );
1693    }
1694}