khive-query 0.14.0

GQL and SPARQL parsers with SQL compiler for knowledge graph queries.
Documentation
use super::*;

include!("sparql_tests/patterns_tests.rs");

include!("sparql_tests/repeated_constraints_tests.rs");

#[test]
fn disconnected_triples_rejected() {
    let err = parse("SELECT ?a ?d WHERE { ?a :extends ?b . ?c :implements ?d . }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "expected Unsupported, got {err:?}"
    );
}

#[test]
fn branched_triples_rejected() {
    let err = parse("SELECT ?a ?b ?c WHERE { ?a :extends ?b . ?a :implements ?c . }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "expected Unsupported, got {err:?}"
    );
}

#[test]
fn disconnected_kind_constraint_rejected() {
    let err = parse("SELECT ?a WHERE { ?a :extends ?b . ?c a :concept . }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "expected Unsupported for disconnected kind constraint, got {err:?}"
    );
}

#[test]
fn disconnected_property_constraint_rejected() {
    let err = parse("SELECT ?a WHERE { ?a :extends ?b . ?c :name 'LoRA' . }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "expected Unsupported for disconnected property constraint, got {err:?}"
    );
}

#[test]
fn rejects_trailing_input_after_limit() {
    let err =
        parse("SELECT ?a WHERE { ?a a :concept . ?a :extends ?b . } LIMIT 10 GARBAGE").unwrap_err();
    assert!(
        err.to_string().contains("unexpected trailing input"),
        "expected trailing-input parse error, got {err}"
    );
}

#[test]
fn sparql_offset_remains_outside_the_dialect() {
    let err = parse("SELECT ?a WHERE { ?a :extends ?b . } LIMIT 10 OFFSET 10").unwrap_err();
    assert!(
        err.to_string().contains("unexpected trailing input"),
        "SPARQL OFFSET must not leak in through the shared AST, got {err}"
    );
}

#[test]
fn rejects_trailing_input_without_limit() {
    let err =
        parse("SELECT ?a WHERE { ?a a :concept . ?a :extends ?b . } and then some").unwrap_err();
    assert!(
        err.to_string().contains("unexpected trailing input"),
        "expected trailing-input parse error, got {err}"
    );
}

#[test]
fn sparql_insert_data_rejected_with_readonly_message() {
    let err = parse("INSERT DATA { <a> :extends <b> }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "INSERT DATA must return Unsupported; got {err:?}"
    );
    let msg = err.to_string();
    assert!(
        msg.contains("read-only"),
        "error must mention 'read-only'; got: {msg}"
    );
    assert!(
        msg.contains("create") && msg.contains("update") && msg.contains("delete"),
        "error must name the mutation verbs; got: {msg}"
    );
}

#[test]
fn sparql_delete_data_rejected_with_readonly_message() {
    let err = parse("DELETE DATA { <a> :extends <b> }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "DELETE DATA must return Unsupported; got {err:?}"
    );
    let msg = err.to_string();
    assert!(msg.contains("read-only"), "got: {msg}");
}

#[test]
fn sparql_delete_where_rejected_with_readonly_message() {
    let err = parse("DELETE WHERE { ?s :extends ?o }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "DELETE WHERE must return Unsupported; got {err:?}"
    );
}

#[test]
fn sparql_load_rejected_with_readonly_message() {
    let err = parse("LOAD <http://example.org/graph>").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "LOAD must return Unsupported; got {err:?}"
    );
}

#[test]
fn sparql_select_still_compiles_after_write_guard() {
    let q = parse("SELECT ?a WHERE { ?a :extends ?b . }").unwrap();
    assert!(!q.pattern.elements.is_empty(), "valid SELECT must parse");
}

#[test]
fn sparql_with_delete_where_rejected() {
    let err = parse("WITH <http://g> DELETE { ?s ?p ?o } WHERE { ?s ?p ?o }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "WITH … DELETE … must return Unsupported; got {err:?}"
    );
    let msg = err.to_string();
    assert!(
        msg.contains("read-only"),
        "error must mention 'read-only'; got: {msg}"
    );
    assert!(
        msg.contains("create") && msg.contains("update") && msg.contains("delete"),
        "error must name the mutation verbs; got: {msg}"
    );
}

#[test]
fn sparql_prefixed_insert_data_rejected() {
    let err = parse("PREFIX ex: <http://example.org/> INSERT DATA { ex:a ex:b ex:c }").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "prefixed INSERT DATA must return Unsupported; got {err:?}"
    );
    let msg = err.to_string();
    assert!(msg.contains("read-only"), "got: {msg}");
}

#[test]
fn sparql_clear_graph_rejected() {
    let err = parse("CLEAR GRAPH <http://example.org/graph>").unwrap_err();
    assert!(
        matches!(err, QueryError::Unsupported(_)),
        "CLEAR GRAPH must return Unsupported; got {err:?}"
    );
    let msg = err.to_string();
    assert!(msg.contains("read-only"), "got: {msg}");
}

#[test]
fn sparql_prefixed_select_write_guard_passes_through() {
    let err = parse("PREFIX ex: <http://example.org/> SELECT ?s WHERE { ?s ?p ?o }").unwrap_err();
    assert!(
        !matches!(err, QueryError::Unsupported(_)),
        "prefixed SELECT must not be rejected by the write guard; got {err:?}"
    );
}