| 142 | } |
| 143 | |
| 144 | fn parse_atom(tokens: &[String], pos: &mut usize) -> Result<FtsQuery> { |
| 145 | if *pos >= tokens.len() { |
| 146 | return Err(SqlError::Parse { |
| 147 | detail: "tsquery: unexpected end of input".into(), |
| 148 | }); |
| 149 | } |
| 150 | if tokens[*pos] == "(" { |
| 151 | *pos += 1; |
| 152 | let inner = parse_or(tokens, pos)?; |
| 153 | if *pos >= tokens.len() || tokens[*pos] != ")" { |
| 154 | return Err(SqlError::Parse { |
| 155 | detail: "tsquery: unmatched '('".into(), |
| 156 | }); |
| 157 | } |
| 158 | *pos += 1; |
| 159 | return Ok(inner); |
| 160 | } |
| 161 | // Must be a term token. |
| 162 | let term = tokens[*pos].clone(); |
| 163 | if term == ")" || term == "&" || term == "|" || term == "!" { |
| 164 | return Err(SqlError::Parse { |
| 165 | detail: format!("tsquery: unexpected operator '{term}'"), |
| 166 | }); |
| 167 | } |
| 168 | *pos += 1; |
| 169 | // Check for ':*' suffix (prefix search). |
| 170 | if *pos < tokens.len() && tokens[*pos] == ":*" { |
| 171 | *pos += 1; |
| 172 | return Ok(FtsQuery::Prefix(term)); |
| 173 | } |
| 174 | Ok(FtsQuery::Plain { |
| 175 | text: term, |
| 176 | fuzzy: false, |
| 177 | }) |
| 178 | } |
| 179 | |
| 180 | // ── Tests ──────────────────────────────────────────────────────────────────── |
| 181 | |