Skip to main content

uqa_sql/semantics/
graph_functions.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Graph-name selection and argument rules for SQL graph table functions.
8
9use crate::SQLError;
10use uqa_core::Value;
11
12pub trait GraphNameCatalog {
13    fn list_graphs(&self) -> Result<Vec<String>, SQLError>;
14}
15
16pub(crate) fn default_graph_name(
17    catalog: &dyn GraphNameCatalog,
18    function_name: &str,
19) -> Result<String, SQLError> {
20    let graphs = catalog.list_graphs()?;
21    match graphs.as_slice() {
22        [name] => Ok(name.clone()),
23        [] => Err(SQLError::Unsupported(format!(
24            "{function_name} requires a graph argument because no graph is registered"
25        ))),
26        _ => Err(SQLError::Unsupported(format!(
27            "{function_name} requires a graph argument because multiple graphs are registered: {}",
28            graphs.join(", ")
29        ))),
30    }
31}
32
33pub fn expect_optional_graph_value(
34    catalog: &dyn GraphNameCatalog,
35    value: Option<&Value>,
36    function_name: &str,
37) -> Result<String, SQLError> {
38    match value {
39        Some(Value::Str(name)) => Ok(name.clone()),
40        Some(other) => Err(SQLError::TypeMismatch(format!(
41            "{function_name}.graph must be string, got {other:?}"
42        ))),
43        None => default_graph_name(catalog, function_name),
44    }
45}
46
47pub fn centrality_graph(
48    catalog: &dyn GraphNameCatalog,
49    evaluated: &[Value],
50    lower: &str,
51) -> Result<String, SQLError> {
52    if evaluated.len() > 1 {
53        return Err(SQLError::TypeMismatch(format!(
54            "{lower} accepts at most one graph argument"
55        )));
56    }
57    let graph = expect_optional_graph_value(catalog, evaluated.first(), lower)?;
58    Ok(graph)
59}
60
61pub fn regular_path_arguments(
62    catalog: &dyn GraphNameCatalog,
63    evaluated: &[Value],
64) -> Result<(String, u64, String), SQLError> {
65    if !(2..=3).contains(&evaluated.len()) {
66        return Err(SQLError::TypeMismatch(
67            "rpq requires 2 or 3 args (expr, start [, graph])".into(),
68        ));
69    }
70    let expr_str = match &evaluated[0] {
71        Value::Str(s) => s.clone(),
72        _ => return Err(SQLError::TypeMismatch("rpq.expr must be string".into())),
73    };
74    let start = match &evaluated[1] {
75        Value::Int(n) => u64::try_from(*n).map_err(|_| {
76            SQLError::TypeMismatch("rpq.start must be a non-negative integer".into())
77        })?,
78        _ => return Err(SQLError::TypeMismatch("rpq.start must be integer".into())),
79    };
80    let graph = expect_optional_graph_value(catalog, evaluated.get(2), "rpq")?;
81    Ok((expr_str, start, graph))
82}