use reddb_types::function_catalog::{FunctionKind, FUNCTION_CATALOG};
use crate::ast::QueryExpr;
use crate::parser::{parse as parse_rql, ParseError, ParseErrorKind};
use crate::planner::QueryOptimizer;
pub const RESOURCE_URI: &str = "reddb://knowledge/rql";
pub const RESOURCE_TITLE: &str = "RedDB RQL Reference";
pub const RESOURCE_DESCRIPTION: &str =
"Generated RQL grammar reference: every keyword and built-in function the engine knows.";
pub const LLMS_BEGIN_MARKER: &str = "<!-- BEGIN GENERATED: rql -->";
pub const LLMS_END_MARKER: &str = "<!-- END GENERATED: rql -->";
pub const KEYWORDS: &[&str] = &[
"ACK",
"ADD",
"ALGORITHM",
"ALL",
"ALTER",
"ANALYZE",
"AND",
"AS",
"ASC",
"ATTACH",
"AVG",
"BEGIN",
"BETWEEN",
"BY",
"CASCADE",
"CENTRALITY",
"CLUSTERING",
"COLLECTION",
"COLUMN",
"COMMIT",
"COMMUNITY",
"COMPONENTS",
"COMPRESS",
"CONTAINS",
"COPY",
"COSINE",
"COUNT",
"CREATE",
"CROSS",
"CURRENT",
"CYCLES",
"DATA",
"DEFAULT",
"DELETE",
"DELIMITER",
"DEPTH",
"DESC",
"DETACH",
"DIRECTION",
"DISABLE",
"DISTINCT",
"DOCUMENT",
"DROP",
"EDGE",
"ENABLE",
"ENDS",
"ENRICH",
"EXISTS",
"EXPLAIN",
"FALSE",
"FIRST",
"FOLLOWING",
"FOR",
"FOREIGN",
"FORMAT",
"FROM",
"FULL",
"FUSION",
"FUZZY",
"GAP",
"GRAPH",
"GROUP",
"HASH",
"HEADER",
"HYBRID",
"IF",
"IN",
"INCLUDE",
"INCREMENT",
"INDEX",
"INNER",
"INNERPRODUCT",
"INNER_PRODUCT",
"INSERT",
"INTERSECTION",
"INTO",
"IS",
"JOIN",
"JSON",
"K",
"KEY",
"KV",
"L2",
"LAST",
"LEFT",
"LEVEL",
"LIKE",
"LIMIT",
"LIST",
"MATCH",
"MATERIALIZED",
"MAX",
"MAXITERATIONS",
"MAXLENGTH",
"MAX_ITERATIONS",
"MAX_LENGTH",
"METADATA",
"METRIC",
"MIN",
"MINSCORE",
"MIN_SCORE",
"MODE",
"NACK",
"NEIGHBORHOOD",
"NODE",
"NOT",
"NULL",
"NULLS",
"OF",
"OFFSET",
"ON",
"OPTIONS",
"OR",
"ORDER",
"OUTER",
"OVER",
"PARTITION",
"PATH",
"PEEK",
"POLICY",
"POP",
"PRECEDING",
"PRIMARY",
"PRIORITY",
"PROPERTIES",
"PURGE",
"PUSH",
"QUEUE",
"RANGE",
"RECURSIVE",
"REFRESH",
"RELEASE",
"RENAME",
"RERANK",
"RETENTION",
"RETURN",
"RETURNING",
"RIGHT",
"ROLLBACK",
"ROW",
"ROWS",
"RRF",
"SAVEPOINT",
"SCHEMA",
"SEARCH",
"SECURITY",
"SELECT",
"SEQUENCE",
"SERVER",
"SESSIONIZE",
"SET",
"SHORTESTPATH",
"SHORTEST_PATH",
"SIMILAR",
"START",
"STARTS",
"STRATEGY",
"SUM",
"TABLE",
"TEXT",
"THRESHOLD",
"TIMESERIES",
"TO",
"TOPOLOGICALSORT",
"TOPOLOGICAL_SORT",
"TRANSACTION",
"TRAVERSE",
"TREE",
"TRUE",
"TRUNCATE",
"UNBOUNDED",
"UNION",
"UNIQUE",
"UPDATE",
"USING",
"VACUUM",
"VALUES",
"VECTOR",
"VECTORS",
"VIA",
"VIEW",
"WEIGHT",
"WHERE",
"WITH",
"WORK",
"WRAPPER",
];
pub fn function_names() -> Vec<&'static str> {
let mut names: Vec<&'static str> = FUNCTION_CATALOG.iter().map(|entry| entry.name).collect();
names.sort_unstable();
names.dedup();
names
}
fn function_names_of_kind(kind: FunctionKind) -> Vec<&'static str> {
let mut names: Vec<&'static str> = Vec::new();
for entry in FUNCTION_CATALOG {
if entry.kind == kind && !names.contains(&entry.name) {
let first_kind = FUNCTION_CATALOG
.iter()
.find(|candidate| candidate.name == entry.name)
.map(|first| first.kind);
if first_kind == Some(kind) {
names.push(entry.name);
}
}
}
names.sort_unstable();
names.dedup();
names
}
fn render_code_list(names: &[&str]) -> String {
names
.iter()
.map(|name| format!("`{name}`"))
.collect::<Vec<_>>()
.join(", ")
}
pub fn rql_reference_markdown() -> String {
let keywords = KEYWORDS;
let functions = function_names();
let mut out = String::new();
out.push_str("# RedDB RQL Reference\n\n");
out.push_str(
"RQL (RedDB Query Language) is RedDB's SQL-family query surface across its \
multi-model store (documents, key-value, queues, graph, vault, config, and \
RQL-tabular collections).\n\n",
);
out.push_str(
"This reference is generated from the `reddb-io-rql` lexer (keyword authority) \
and the `reddb-io-types` function catalog. Do not edit by hand — regenerate from \
the engine.\n\n",
);
out.push_str(&format!("## Keywords ({})\n\n", keywords.len()));
out.push_str(
"RQL recognises the following reserved keywords (case-insensitive at the \
lexer):\n\n",
);
out.push_str(&render_code_list(keywords));
out.push_str("\n\n");
out.push_str(&format!("## Functions ({})\n\n", functions.len()));
out.push_str("RQL provides these built-in functions, grouped by kind:\n\n");
for (kind, title) in [
(FunctionKind::Aggregate, "Aggregate functions"),
(FunctionKind::Scalar, "Scalar functions"),
(FunctionKind::Window, "Window functions"),
(FunctionKind::Volatile, "Volatile functions"),
] {
let names = function_names_of_kind(kind);
if names.is_empty() {
continue;
}
out.push_str(&format!("### {title}\n\n"));
out.push_str(&render_code_list(&names));
out.push_str("\n\n");
}
while out.ends_with("\n\n") {
out.pop();
}
out
}
pub fn rql_llms_section() -> String {
format!(
"{begin}\n{body}\n{end}",
begin = LLMS_BEGIN_MARKER,
body = rql_reference_markdown(),
end = LLMS_END_MARKER,
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RqlDiagnostic {
pub message: String,
pub line: u32,
pub column: u32,
pub offset: u32,
pub kind: String,
pub expected: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RqlValidation {
pub valid: bool,
pub statement: Option<String>,
pub has_with_clause: bool,
pub error: Option<RqlDiagnostic>,
}
#[derive(Debug, Clone)]
pub struct RqlExplanation {
pub validation: RqlValidation,
pub ast: Option<String>,
pub optimized_ast: Option<String>,
pub applied_passes: Vec<String>,
}
fn parse_error_kind_label(kind: &ParseErrorKind) -> &'static str {
match kind {
ParseErrorKind::Syntax => "syntax",
ParseErrorKind::DepthLimit { .. } => "depth_limit",
ParseErrorKind::InputTooLarge { .. } => "input_too_large",
ParseErrorKind::IdentifierTooLong { .. } => "identifier_too_long",
ParseErrorKind::TokenLimit { .. } => "token_limit",
ParseErrorKind::ValueOutOfRange { .. } => "value_out_of_range",
ParseErrorKind::UnsupportedToken { .. } => "unsupported_token",
}
}
fn diagnostic_from_error(err: &ParseError) -> RqlDiagnostic {
RqlDiagnostic {
message: err.message.clone(),
line: err.position.line,
column: err.position.column,
offset: err.position.offset,
kind: parse_error_kind_label(&err.kind).to_string(),
expected: err.expected.clone(),
}
}
fn statement_kind(expr: &QueryExpr) -> String {
format!("{expr:?}")
.chars()
.take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
.collect()
}
pub fn validate_rql(input: &str) -> RqlValidation {
match parse_rql(input) {
Ok(query) => RqlValidation {
valid: true,
statement: Some(statement_kind(&query.query)),
has_with_clause: query.with_clause.is_some(),
error: None,
},
Err(err) => RqlValidation {
valid: false,
statement: None,
has_with_clause: false,
error: Some(diagnostic_from_error(&err)),
},
}
}
pub fn explain_rql(input: &str) -> RqlExplanation {
match parse_rql(input) {
Ok(query) => {
let validation = RqlValidation {
valid: true,
statement: Some(statement_kind(&query.query)),
has_with_clause: query.with_clause.is_some(),
error: None,
};
let ast = format!("{:#?}", query.query);
let (optimized, applied_passes) = QueryOptimizer::new().optimize(query.query);
RqlExplanation {
validation,
ast: Some(ast),
optimized_ast: Some(format!("{optimized:#?}")),
applied_passes,
}
}
Err(err) => RqlExplanation {
validation: RqlValidation {
valid: false,
statement: None,
has_with_clause: false,
error: Some(diagnostic_from_error(&err)),
},
ast: None,
optimized_ast: None,
applied_passes: Vec::new(),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::{Lexer, Token};
#[test]
fn keywords_are_recognised() {
for &keyword in KEYWORDS {
let mut lexer = Lexer::new(keyword);
let spanned = lexer
.next_token()
.unwrap_or_else(|err| panic!("keyword {keyword:?} failed to lex: {err:?}"));
assert!(
!matches!(spanned.token, Token::Ident(_)),
"{keyword:?} is in the knowledge catalog but the lexer treats it as a \
plain identifier — it is not a real RQL keyword"
);
}
}
#[test]
fn keyword_list_is_sorted_and_unique() {
let mut sorted = KEYWORDS.to_vec();
sorted.sort_unstable();
assert_eq!(KEYWORDS.to_vec(), sorted, "KEYWORDS must be sorted");
sorted.dedup();
assert_eq!(
sorted.len(),
KEYWORDS.len(),
"KEYWORDS must not contain duplicates"
);
}
#[test]
fn non_keyword_lexes_as_identifier() {
let mut lexer = Lexer::new("not_a_keyword_xyzzy");
let spanned = lexer.next_token().expect("lex");
assert!(matches!(spanned.token, Token::Ident(_)));
}
#[test]
fn reference_lists_every_function() {
let reference = rql_reference_markdown();
for name in function_names() {
assert!(
reference.contains(&format!("`{name}`")),
"function {name:?} from FUNCTION_CATALOG is missing from the generated \
RQL reference"
);
}
}
#[test]
fn reference_lists_every_keyword() {
let reference = rql_reference_markdown();
for &keyword in KEYWORDS {
assert!(
reference.contains(&format!("`{keyword}`")),
"keyword {keyword:?} is missing from the generated RQL reference"
);
}
}
#[test]
fn reference_is_deterministic() {
assert_eq!(rql_reference_markdown(), rql_reference_markdown());
}
#[test]
fn validate_accepts_valid_select() {
let verdict = validate_rql("SELECT * FROM users");
assert!(verdict.valid, "expected valid: {verdict:?}");
assert_eq!(verdict.statement.as_deref(), Some("Table"));
assert!(verdict.error.is_none());
}
#[test]
fn validate_reports_with_clause() {
let verdict = validate_rql("WITH t AS (SELECT * FROM users) SELECT * FROM t");
assert!(verdict.valid, "expected valid: {verdict:?}");
assert!(
verdict.has_with_clause,
"expected has_with_clause: {verdict:?}"
);
}
#[test]
fn validate_rejects_invalid_with_structured_error() {
let verdict = validate_rql("SELECT * FROM");
assert!(!verdict.valid, "expected invalid: {verdict:?}");
assert!(verdict.statement.is_none());
let err = verdict.error.expect("structured error");
assert!(!err.message.is_empty(), "message should be populated");
assert!(err.line >= 1, "line is 1-indexed: {err:?}");
assert!(!err.kind.is_empty(), "kind label should be populated");
}
#[test]
fn explain_returns_ast_and_plan() {
let explanation = explain_rql("SELECT * FROM users WHERE id = 1");
assert!(explanation.validation.valid);
let ast = explanation.ast.expect("ast present");
assert!(ast.contains("Table"), "ast should render the query: {ast}");
assert!(explanation.optimized_ast.is_some());
}
#[test]
fn explain_on_invalid_omits_ast() {
let explanation = explain_rql("NOT REAL RQL @@@");
assert!(!explanation.validation.valid);
assert!(explanation.ast.is_none());
assert!(explanation.optimized_ast.is_none());
assert!(explanation.applied_passes.is_empty());
}
#[test]
fn llms_section_wraps_reference() {
let section = rql_llms_section();
assert!(section.starts_with(LLMS_BEGIN_MARKER));
assert!(section.ends_with(LLMS_END_MARKER));
assert!(section.contains(&rql_reference_markdown()));
}
}