Improve operation error on vector filters

This commit is contained in:
Mubelotix
2025-08-13 10:32:28 +02:00
parent b5ba0e42b3
commit f6559258ce
3 changed files with 31 additions and 24 deletions

View File

@@ -164,21 +164,24 @@ fn parse_vectors(input: Span) -> IResult<(Token, Option<Token>, VectorFilter<'_>
Ok((input, (Token::from(fid), Some(embedder_name), filter))) Ok((input, (Token::from(fid), Some(embedder_name), filter)))
} }
/// vectors_exists = vectors "EXISTS" /// vectors_exists = vectors ("EXISTS" | ("NOT" WS+ "EXISTS"))
pub fn parse_vectors_exists(input: Span) -> IResult<FilterCondition> { pub fn parse_vectors_exists(input: Span) -> IResult<FilterCondition> {
let (input, (fid, embedder, filter)) = terminated(parse_vectors, tag("EXISTS"))(input)?;
Ok((input, FilterCondition::VectorExists { fid, embedder, filter }))
}
/// vectors_not_exists = vectors "NOT" WS+ "EXISTS"
pub fn parse_vectors_not_exists(input: Span) -> IResult<FilterCondition> {
let (input, (fid, embedder, filter)) = parse_vectors(input)?; let (input, (fid, embedder, filter)) = parse_vectors(input)?;
let (input, _) = tuple((tag("NOT"), multispace1, tag("EXISTS")))(input)?; // Try parsing "EXISTS" first
Ok(( if let Ok((input, _)) = tag::<_, _, ()>("EXISTS")(input) {
input, return Ok((input, FilterCondition::VectorExists { fid, embedder, filter }));
FilterCondition::Not(Box::new(FilterCondition::VectorExists { fid, embedder, filter })), }
))
// Try parsing "NOT EXISTS"
if let Ok((input, _)) = tuple::<_, _, (), _>((tag("NOT"), multispace1, tag("EXISTS")))(input) {
return Ok((
input,
FilterCondition::Not(Box::new(FilterCondition::VectorExists { fid, embedder, filter })),
));
}
Err(crate::Error::new_failure_from_kind(input, ErrorKind::VectorFilterOperation))
} }
/// contains = value "CONTAINS" value /// contains = value "CONTAINS" value

View File

@@ -83,6 +83,7 @@ pub enum ErrorKind<'a> {
VectorFilterInvalidEmbedder, VectorFilterInvalidEmbedder,
VectorFilterMissingFragment, VectorFilterMissingFragment,
VectorFilterInvalidFragment, VectorFilterInvalidFragment,
VectorFilterOperation,
InvalidPrimary, InvalidPrimary,
InvalidEscapedNumber, InvalidEscapedNumber,
ExpectedEof, ExpectedEof,
@@ -210,6 +211,9 @@ impl Display for Error<'_> {
ErrorKind::VectorFilterInvalidEmbedder => { ErrorKind::VectorFilterInvalidEmbedder => {
writeln!(f, "The vector filter's embedder is invalid.")? writeln!(f, "The vector filter's embedder is invalid.")?
} }
ErrorKind::VectorFilterOperation => {
writeln!(f, "Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.")?
}
ErrorKind::ReservedKeyword(word) => { ErrorKind::ReservedKeyword(word) => {
writeln!(f, "`{word}` is a reserved keyword and thus cannot be used as a field name unless it is put inside quotes. Use \"{word}\" or \'{word}\' instead.")? writeln!(f, "`{word}` is a reserved keyword and thus cannot be used as a field name unless it is put inside quotes. Use \"{word}\" or \'{word}\' instead.")?
} }

View File

@@ -65,7 +65,7 @@ use nom_locate::LocatedSpan;
pub(crate) use value::parse_value; pub(crate) use value::parse_value;
use value::word_exact; use value::word_exact;
use crate::condition::{parse_vectors_exists, parse_vectors_not_exists}; use crate::condition::parse_vectors_exists;
use crate::error::IResultExt; use crate::error::IResultExt;
pub type Span<'a> = LocatedSpan<&'a str, &'a str>; pub type Span<'a> = LocatedSpan<&'a str, &'a str>;
@@ -525,7 +525,7 @@ fn parse_primary(input: Span, depth: usize) -> IResult<FilterCondition> {
parse_is_not_null, parse_is_not_null,
parse_is_empty, parse_is_empty,
parse_is_not_empty, parse_is_not_empty,
alt((parse_vectors_exists, parse_vectors_not_exists, parse_exists, parse_not_exists)), alt((parse_vectors_exists, parse_exists, parse_not_exists)),
parse_to, parse_to,
parse_contains, parse_contains,
parse_not_contains, parse_not_contains,
@@ -1002,16 +1002,16 @@ pub mod tests {
); );
insta::assert_snapshot!(p(r#"_vectors _vectors EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors _vectors EXISTS"#), @r"
Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `CONTAINS`, `NOT CONTAINS`, `STARTS WITH`, `NOT STARTS WITH`, `_geoRadius`, or `_geoBoundingBox` at `_vectors _vectors EXISTS`. Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.
1:25 _vectors _vectors EXISTS 10:25 _vectors _vectors EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors. embedderName EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors. embedderName EXISTS"#), @r"
Was expecting embedder name but found nothing. Was expecting embedder name but found nothing.
10:11 _vectors. embedderName EXISTS 10:11 _vectors. embedderName EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors .embedderName EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors .embedderName EXISTS"#), @r"
Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `CONTAINS`, `NOT CONTAINS`, `STARTS WITH`, `NOT STARTS WITH`, `_geoRadius`, or `_geoBoundingBox` at `_vectors .embedderName EXISTS`. Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.
1:30 _vectors .embedderName EXISTS 10:30 _vectors .embedderName EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors.embedderName. EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors.embedderName. EXISTS"#), @r"
The vector filter has leftover tokens. The vector filter has leftover tokens.
@@ -1038,20 +1038,20 @@ pub mod tests {
33:40 _vectors.embedderName.fragments. EXISTS 33:40 _vectors.embedderName.fragments. EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors.embedderName.fragments.test test EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors.embedderName.fragments.test test EXISTS"#), @r"
Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `CONTAINS`, `NOT CONTAINS`, `STARTS WITH`, `NOT STARTS WITH`, `_geoRadius`, or `_geoBoundingBox` at `_vectors.embedderName.fragments.test test EXISTS`. Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.
1:49 _vectors.embedderName.fragments.test test EXISTS 38:49 _vectors.embedderName.fragments.test test EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors.embedderName.fragments. test EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors.embedderName.fragments. test EXISTS"#), @r"
The vector filter's fragment is invalid. The vector filter's fragment is invalid.
33:45 _vectors.embedderName.fragments. test EXISTS 33:45 _vectors.embedderName.fragments. test EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors.embedderName .fragments. test EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors.embedderName .fragments. test EXISTS"#), @r"
Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `CONTAINS`, `NOT CONTAINS`, `STARTS WITH`, `NOT STARTS WITH`, `_geoRadius`, or `_geoBoundingBox` at `_vectors.embedderName .fragments. test EXISTS`. Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.
1:46 _vectors.embedderName .fragments. test EXISTS 23:46 _vectors.embedderName .fragments. test EXISTS
"); ");
insta::assert_snapshot!(p(r#"_vectors.embedderName .fragments.test EXISTS"#), @r" insta::assert_snapshot!(p(r#"_vectors.embedderName .fragments.test EXISTS"#), @r"
Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `CONTAINS`, `NOT CONTAINS`, `STARTS WITH`, `NOT STARTS WITH`, `_geoRadius`, or `_geoBoundingBox` at `_vectors.embedderName .fragments.test EXISTS`. Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.
1:45 _vectors.embedderName .fragments.test EXISTS 23:45 _vectors.embedderName .fragments.test EXISTS
"); ");
insta::assert_snapshot!(p(r#"NOT OR EXISTS AND EXISTS NOT EXISTS"#), @r###" insta::assert_snapshot!(p(r#"NOT OR EXISTS AND EXISTS NOT EXISTS"#), @r###"