qlue-ls 3.4.2

A language server for SPARQL
Documentation
use ll_sparql_parser::{
    ast::{AstNode, QueryUnit},
    parse_query,
};
use urlencoding::encode;

use crate::server::{configuration::BackendConfiguration, lsp::SparqlEngine};

/// Minimal query used to check whether an endpoint answers SPARQL requests.
const HEALTH_CHECK_QUERY: &str = "SELECT * WHERE { VALUES ?x {1} } LIMIT 1";

/// Build the URL used to check whether a backend is available.
///
/// An explicitly configured `health_check_url` wins. Otherwise QLever backends
/// are pinged via their dedicated `/ping` endpoint (cheaper than running a
/// query); every other engine gets a minimal SPARQL query.
pub(crate) fn health_check_url(backend: &BackendConfiguration) -> String {
    if let Some(url) = &backend.health_check_url {
        return url.clone();
    }
    match backend.engine {
        Some(SparqlEngine::QLever) => format!("{}/ping", backend.url.trim_end_matches('/')),
        _ => format!("{}?query={}", backend.url, encode(HEALTH_CHECK_QUERY)),
    }
}

/// Wrap a SELECT query in an outer `SELECT * WHERE { ... }` and append
/// `LIMIT`/`OFFSET` clauses, leaving any surrounding prologue/values intact.
///
/// Returns `None` if there is nothing to do (no limit and zero offset) or if
/// the input does not parse as a query containing a SELECT.
pub(crate) fn add_limit_offset_to_query(
    query: &str,
    limit: Option<usize>,
    offset: usize,
) -> Option<String> {
    if limit.is_none() && offset == 0 {
        return None;
    }
    // WARNING: The inner SELECT's `ORDER BY` is not guaranteed by the SPARQL
    // spec to be preserved once it is wrapped as a subquery under an outer
    // `SELECT *`. QLever happens to preserve it, but pagination over a query
    // without a total `ORDER BY` may yield overlapping or skipped rows across
    // separate LIMIT/OFFSET requests.
    let syntax_tree = QueryUnit::cast(parse_query(query).0)?;
    let select_query = syntax_tree.select_query()?;
    let limit_clause = limit.map_or(String::new(), |limit| format!("LIMIT {limit}\n"));
    Some(format!(
        "{}SELECT * WHERE {{\n{}\n}}\n{}OFFSET {}{}",
        &query[0..select_query.syntax().text_range().start().into()],
        select_query.text(),
        limit_clause,
        offset,
        &query[select_query.syntax().text_range().end().into()..]
    ))
}