Skip to main content

uqa_sql/retrieval/
description.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Inspect vector-call identity while leaving unsafe or unavailable arguments unevaluated.
8
9use super::{calls, column_name, RetrievalConstants, RetrievalExpr};
10use crate::{registry::FunctionKind, SQLError, ScalarExpr};
11
12#[cfg(test)]
13mod tests;
14
15pub struct VectorCallDescription {
16    pub field: Option<String>,
17    pub qualifier: Option<String>,
18    pub calibrated: bool,
19    pub bound: Option<RetrievalExpr>,
20    pub invalid_arguments: bool,
21}
22
23pub fn describe_vector_call(
24    expression: &ScalarExpr,
25    constants: &RetrievalConstants<'_>,
26    can_evaluate: &dyn Fn(&ScalarExpr) -> bool,
27) -> Result<Option<VectorCallDescription>, SQLError> {
28    let ScalarExpr::Func { name, args, .. } = expression else {
29        return Ok(None);
30    };
31    let calibrated = match crate::registry::lookup(name) {
32        Some(FunctionKind::KNNMatch) => false,
33        Some(FunctionKind::CalibratedVectorMatch) => true,
34        _ => return Ok(None),
35    };
36    calls::validate_operator_function_arity(name, args.len())?;
37    let field = column_name(&args[0]).or_else(|| {
38        (calibrated && can_evaluate(&args[0]))
39            .then(|| calls::field_name_arg(&args[0], constants))
40            .flatten()
41    });
42    let qualifier = match &args[0] {
43        ScalarExpr::QualifiedColumn { qualifier, .. } => Some(qualifier.clone()),
44        _ => None,
45    };
46    let available = field.is_some() && args.iter().skip(1).all(can_evaluate);
47    let bound = if available {
48        if calibrated {
49            calls::try_lower_calibrated_vector_match(args, constants).ok()
50        } else {
51            calls::try_lower_knn_match(args, constants).ok()
52        }
53    } else {
54        None
55    };
56    Ok(Some(VectorCallDescription {
57        field,
58        qualifier,
59        calibrated,
60        invalid_arguments: available && bound.is_none(),
61        bound,
62    }))
63}