qql-core 0.1.3

Parser, typed AST, validation, and transformations for the Qdrant Query Language
Documentation
#![allow(dead_code)]

use crate::error::{QqlError, Span};
use pest::error::InputLocation;
use pest::Parser as _;

#[derive(pest_derive::Parser)]
#[grammar = "grammar/qql.generated.pest"]
struct CanonicalSyntaxParser;

pub(super) fn validate_statement(input: &str) -> Result<(), QqlError> {
    CanonicalSyntaxParser::parse(Rule::single_statement, input)
        .map(|_| ())
        .map_err(grammar_error)
}

pub(super) fn validate_script(input: &str) -> Result<(), QqlError> {
    CanonicalSyntaxParser::parse(Rule::script, input)
        .map(|_| ())
        .map_err(grammar_error)
}

fn grammar_error(error: pest::error::Error<Rule>) -> QqlError {
    let span = match error.location {
        InputLocation::Pos(position) => Span::point(position),
        InputLocation::Span((start, end)) => Span::new(start, end),
    };
    QqlError::parse(
        "QQL-PARSE-GRAMMAR",
        "input does not match the canonical QQL grammar",
        span,
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn canonical_grammar_accepts_scripts() {
        validate_script("SHOW COLLECTIONS; COUNT FROM docs;").unwrap();
    }

    #[test]
    fn canonical_grammar_rejects_legacy_statements() {
        for source in [
            "SELECT * FROM docs",
            "INSERT INTO docs VALUES {id: 1}",
            "BOOST ($score * 2)",
            "CREATE COLLECTION docs VECTORS (dense VECTOR (4, COSINE))",
            "CREATE COLLECTION docs (VECTOR (4, COSINE))",
            "CREATE COLLECTION docs (dense (4, COSINE))",
            "CREATE COLLECTION docs (dense VECTOR (4, COSINE) WITH VECTORS (on_disk = true))",
            "ALTER COLLECTION docs WITH QUANTIZE (type = 'scalar')",
            "CREATE SHARD 'tenant' ON COLLECTION docs",
            "QUERY TEXT 'x' FROM docs PARAMS (k = 30)",
            "QUERY TEXT 'x' FROM docs PARAMS (weights = [1.0])",
            "CREATE INDEX ON COLLECTION docs FOR title TYPE 'text'",
        ] {
            assert!(validate_statement(source).is_err(), "{source}");
        }
    }
}