Skip to main content

uqa_sql/semantics/
graph_commands.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! SQL argument coercion and diagnostics for native and AGE graph commands.
8
9use crate::{semantics::retrieval::expect_evaluated_string, SQLError, ScalarExpr};
10use uqa_core::Value;
11
12pub const AGE_INVALID_PARAMETER_VALUE: &str = "22023";
13pub const AGE_UNDEFINED_SCHEMA: &str = "3F000";
14pub const AGE_DUPLICATE_SCHEMA: &str = "42P06";
15pub const AGE_UNDEFINED_TABLE: &str = "42P01";
16pub const AGE_FEATURE_NOT_SUPPORTED: &str = "0A000";
17pub const AGE_DEPENDENT_OBJECTS_STILL_EXIST: &str = "2BP01";
18
19pub fn age_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
20    SQLError::Routine {
21        sqlstate: sqlstate.to_string(),
22        message: message.into(),
23    }
24}
25
26/// Evaluate a `name`/`cstring` argument of an AGE management function.
27/// `null_message` is the AGE error for a SQL NULL argument.
28pub fn eval_age_name_with(
29    expr: &ScalarExpr,
30    null_message: &str,
31    evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
32) -> Result<String, SQLError> {
33    match evaluate(expr)? {
34        Value::Null => Err(age_error(AGE_INVALID_PARAMETER_VALUE, null_message)),
35        Value::Str(s) | Value::FixedChar(s) => Ok(s),
36        other => Err(SQLError::TypeMismatch(format!(
37            "graph name must be a string, got {other:?}"
38        ))),
39    }
40}
41
42pub fn eval_age_graph_name_with(
43    expr: &ScalarExpr,
44    evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
45) -> Result<String, SQLError> {
46    eval_age_name_with(expr, "graph name can not be NULL", evaluate)
47}
48
49pub fn eval_age_bool_with(
50    expr: &ScalarExpr,
51    argument: &str,
52    evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
53) -> Result<bool, SQLError> {
54    match evaluate(expr)? {
55        Value::Bool(value) => Ok(value),
56        other => Err(SQLError::TypeMismatch(format!(
57            "{argument} must be a boolean, got {other:?}"
58        ))),
59    }
60}
61
62pub fn require_age_arity(
63    name: &str,
64    args: &[ScalarExpr],
65    range: std::ops::RangeInclusive<usize>,
66) -> Result<(), SQLError> {
67    if range.contains(&args.len()) {
68        return Ok(());
69    }
70    let expected = if range.start() == range.end() {
71        range.start().to_string()
72    } else {
73        format!("{} or {}", range.start(), range.end())
74    };
75    Err(SQLError::BadArity {
76        name: name.into(),
77        expected,
78        actual: args.len(),
79    })
80}
81
82pub fn graph_create_name(
83    args: &[ScalarExpr],
84    evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
85) -> Result<String, SQLError> {
86    if args.len() != 1 {
87        return Err(SQLError::BadArity {
88            name: "graph_create".into(),
89            expected: "1".into(),
90            actual: args.len(),
91        });
92    }
93    expect_evaluated_string(evaluate(&args[0])?, "graph_create.name")
94}
95
96pub fn graph_drop_name(
97    args: &[ScalarExpr],
98    evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
99) -> Result<String, SQLError> {
100    if !(1..=2).contains(&args.len()) {
101        return Err(SQLError::BadArity {
102            name: "graph_drop".into(),
103            expected: "1 or 2".into(),
104            actual: args.len(),
105        });
106    }
107    expect_evaluated_string(evaluate(&args[0])?, "graph_drop.name")
108}
109
110/// Evaluate the optional cascade argument after the caller reads graph existence.
111pub fn validate_graph_drop_cascade(
112    name: &str,
113    graph_exists: bool,
114    cascade_expr: Option<&ScalarExpr>,
115    evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
116) -> Result<(), SQLError> {
117    if let Some(cascade_expr) = cascade_expr {
118        match evaluate(cascade_expr)? {
119            Value::Bool(false) if graph_exists => {
120                return Err(SQLError::Unsupported(format!(
121                    "cannot drop graph {name:?} without cascade"
122                )));
123            }
124            Value::Bool(_) => {}
125            other => {
126                return Err(SQLError::TypeMismatch(format!(
127                    "graph_drop.cascade must be a boolean, got {other:?}"
128                )));
129            }
130        }
131    }
132    Ok(())
133}