Skip to main content

uqa_sql/semantics/
source_filters.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Source qualifier filters and checked SQL integer carriers.
8
9use crate::{plan::source_projection::QualifierFilters, SQLError, ScalarExpr};
10use uqa_core::Value;
11
12pub fn checked_integer_value<T>(value: T, label: &str) -> Result<Value, SQLError>
13where
14    T: Copy + std::fmt::Display,
15    i64: TryFrom<T>,
16{
17    i64::try_from(value).map(Value::Int).map_err(|_| {
18        SQLError::TypeMismatch(format!("{label} {value} exceeds the SQL BIGINT range"))
19    })
20}
21
22pub fn qualifier_for(qualifier: &str, alias: Option<&str>) -> String {
23    alias.unwrap_or(qualifier).to_string()
24}
25
26pub fn has_filters_for_qualifier(filters: Option<&QualifierFilters>, qual: &str) -> bool {
27    filters
28        .and_then(|filters| filters.get(qual))
29        .is_some_and(|filters| !filters.is_empty())
30}
31
32pub fn combine_filters(filters: impl IntoIterator<Item = ScalarExpr>) -> Option<ScalarExpr> {
33    let mut filters: Vec<ScalarExpr> = filters.into_iter().collect();
34    if filters.len() == 1 {
35        filters.pop()
36    } else if filters.is_empty() {
37        None
38    } else {
39        Some(ScalarExpr::And(filters))
40    }
41}
42
43pub fn qualifier_filter(filters: Option<&QualifierFilters>, qualifier: &str) -> Option<ScalarExpr> {
44    filters
45        .and_then(|filters| filters.get(qualifier))
46        .filter(|filters| !filters.is_empty())
47        .and_then(|filters| combine_filters(filters.iter().cloned()))
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::semantics::doc_id_value;
54
55    #[test]
56    fn combine_filters_handles_empty_and_single_inputs_without_panicking() {
57        assert!(combine_filters(Vec::<ScalarExpr>::new()).is_none());
58        let combined = combine_filters([ScalarExpr::Literal(Value::Bool(true))]);
59        assert!(matches!(
60            combined,
61            Some(ScalarExpr::Literal(Value::Bool(true)))
62        ));
63    }
64
65    #[test]
66    fn document_ids_outside_bigint_are_rejected_at_the_sql_boundary() {
67        assert!(doc_id_value(i64::MAX as u64).is_ok());
68        assert!(doc_id_value(i64::MAX as u64 + 1).is_err());
69    }
70}