Skip to main content

liquid_cache/cache/
expressions.rs

1//! Definitions for cache-aware expressions that can be applied when materializing arrays.
2
3use std::sync::Arc;
4
5use arrow_schema::DataType;
6
7use crate::liquid_array::Date32Field;
8
9/// A typed variant path requested by a query.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
11pub struct VariantRequest {
12    path: Arc<str>,
13    data_type: Arc<DataType>,
14}
15
16impl VariantRequest {
17    /// Create a new typed path request.
18    pub fn new(path: impl Into<Arc<str>>, data_type: DataType) -> Self {
19        Self {
20            path: path.into(),
21            data_type: Arc::new(data_type),
22        }
23    }
24
25    /// Path string for this request.
26    pub fn path(&self) -> &str {
27        self.path.as_ref()
28    }
29
30    /// Requested Arrow data type for this path.
31    pub fn data_type(&self) -> &DataType {
32        self.data_type.as_ref()
33    }
34}
35
36/// Experimental expression descriptor for cache lookups.
37#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
38pub enum CacheExpression {
39    /// Extract a specific component (YEAR/MONTH/DAY/DOW) from a `Date32` column.
40    ExtractDate32 {
41        /// Component to extract (YEAR/MONTH/DAY).
42        field: Date32Field,
43    },
44    /// Extract a field from a variant column via `variant_get`.
45    VariantGet {
46        /// The set of dotted paths requested by the query.
47        requests: Arc<[VariantRequest]>,
48    },
49    /// A column used for predicate evaluation.
50    PredicateColumn,
51    /// A column used primarily for substring search (LIKE '%foo%').
52    SubstringSearch,
53}
54
55impl std::fmt::Display for CacheExpression {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            Self::VariantGet { requests } => {
59                write!(f, "VariantGet[")?;
60                let requests = requests
61                    .iter()
62                    .map(|request| format!("{}:{}", request.path(), request.data_type()))
63                    .collect::<Vec<_>>();
64                write!(f, "{}", requests.join(","))?;
65                write!(f, "]")
66            }
67            Self::ExtractDate32 { field } => {
68                write!(f, "ExtractDate32:{:?}", field)
69            }
70            Self::PredicateColumn => {
71                write!(f, "PredicateColumn")
72            }
73            Self::SubstringSearch => {
74                write!(f, "SubstringSearch")
75            }
76        }
77    }
78}
79
80impl CacheExpression {
81    /// Build an extract expression for a `Date32`/timestamp column.
82    pub fn extract_date32(field: Date32Field) -> Self {
83        Self::ExtractDate32 { field }
84    }
85
86    /// Build a variant-get expression for the provided dotted path.
87    pub fn variant_get(path: impl Into<Arc<str>>, data_type: DataType) -> Self {
88        Self::VariantGet {
89            requests: Arc::from(vec![VariantRequest::new(path, data_type)].into_boxed_slice()),
90        }
91    }
92
93    /// Build a variant-get expression covering multiple paths.
94    pub fn variant_get_many<I, S>(requests: I) -> Self
95    where
96        I: IntoIterator<Item = (S, DataType)>,
97        S: Into<Arc<str>>,
98    {
99        let requests: Vec<VariantRequest> = requests
100            .into_iter()
101            .map(|(path, data_type)| VariantRequest::new(path.into(), data_type))
102            .collect();
103        assert!(
104            !requests.is_empty(),
105            "variant_get_many requires at least one path"
106        );
107        Self::VariantGet {
108            requests: Arc::from(requests.into_boxed_slice()),
109        }
110    }
111
112    /// Build a substring-search expression hint.
113    pub fn substring_search() -> Self {
114        Self::SubstringSearch
115    }
116
117    /// Attempt to parse a metadata value (e.g. `"YEAR"`) into an expression.
118    ///
119    /// The value is compared case-insensitively against supported components.
120    pub fn try_from_date_part_str(value: &str) -> Option<Self> {
121        let upper = value.to_ascii_uppercase();
122        let field = match upper.as_str() {
123            "YEAR" => Date32Field::Year,
124            "MONTH" => Date32Field::Month,
125            "DAY" => Date32Field::Day,
126            "DOW" | "DAYOFWEEK" | "DAY_OF_WEEK" => Date32Field::DayOfWeek,
127            _ => return None,
128        };
129        Some(Self::ExtractDate32 { field })
130    }
131
132    /// Return the requested `Date32` component when this is an extract expression.
133    pub fn as_date32_field(&self) -> Option<Date32Field> {
134        match self {
135            Self::ExtractDate32 { field } => Some(*field),
136            Self::VariantGet { .. } | Self::PredicateColumn | Self::SubstringSearch => None,
137        }
138    }
139
140    /// Return the associated variant path when this is a variant-get expression.
141    pub fn variant_path(&self) -> Option<&str> {
142        match self {
143            Self::VariantGet { requests } => requests.first().map(|request| request.path()),
144            Self::ExtractDate32 { .. } | Self::PredicateColumn | Self::SubstringSearch => None,
145        }
146    }
147
148    /// Return the associated Arrow data type when this is a variant-get expression.
149    pub fn variant_data_type(&self) -> Option<&DataType> {
150        match self {
151            Self::VariantGet { requests } => requests.first().map(|request| request.data_type()),
152            Self::ExtractDate32 { .. } | Self::PredicateColumn | Self::SubstringSearch => None,
153        }
154    }
155
156    /// Return all typed variant paths carried by this expression.
157    pub fn variant_requests(&self) -> Option<&[VariantRequest]> {
158        match self {
159            Self::VariantGet { requests } => Some(requests.as_ref()),
160            Self::ExtractDate32 { .. } | Self::PredicateColumn | Self::SubstringSearch => None,
161        }
162    }
163}