Skip to main content

hawdb_expression/
lib.rs

1pub mod sql;
2
3use hawdb_core::{RelationshipDirection, ValidatedRegex, Value};
4
5mod null_rejection;
6
7pub use null_rejection::{
8    prove_null_rejecting, BindingId, BindingSet, BoundPredicate, BoundScalarExpression,
9    NullRejectionProof, ScalarNullability, TruthSet, TruthValue,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Predicate {
14    And(Vec<Predicate>),
15    Or(Vec<Predicate>),
16    Not(Box<Predicate>),
17    ConstantBool(bool),
18    RelationshipExists {
19        variable: String,
20        rel_type: String,
21        direction: RelationshipDirection,
22        target_label: String,
23    },
24    BoundRelationshipExists {
25        source_variable: String,
26        rel_type: String,
27        direction: RelationshipDirection,
28        target_variable: String,
29    },
30    IdEq {
31        variable: String,
32        value: Value,
33    },
34    IdNotEq {
35        variable: String,
36        value: Value,
37    },
38    IdCompare {
39        variable: String,
40        op: ComparisonOp,
41        value: Value,
42    },
43    IdIn {
44        variable: String,
45        values: Vec<Value>,
46    },
47    PropertyEq {
48        variable: String,
49        property: String,
50        value: Value,
51    },
52    PropertyNotEq {
53        variable: String,
54        property: String,
55        value: Value,
56    },
57    PropertyCompare {
58        variable: String,
59        property: String,
60        op: ComparisonOp,
61        value: Value,
62    },
63    ExpressionEq {
64        expression: ProjectionExpression,
65        value: ProjectionExpression,
66    },
67    ExpressionNotEq {
68        expression: ProjectionExpression,
69        value: ProjectionExpression,
70    },
71    ExpressionCompare {
72        expression: ProjectionExpression,
73        op: ComparisonOp,
74        value: ProjectionExpression,
75    },
76    ExpressionContains {
77        expression: ProjectionExpression,
78        value: ProjectionExpression,
79    },
80    ExpressionIn {
81        expression: ProjectionExpression,
82        values: Vec<Value>,
83    },
84    PropertyListContains {
85        variable: String,
86        property: String,
87        value: Value,
88    },
89    PropertyListContainsLower {
90        variable: String,
91        property: String,
92        value: String,
93    },
94    PropertyContains {
95        variable: String,
96        property: String,
97        value: String,
98    },
99    PropertyStartsWith {
100        variable: String,
101        property: String,
102        value: String,
103    },
104    PropertyEndsWith {
105        variable: String,
106        property: String,
107        value: String,
108    },
109    PropertyRegexMatch {
110        variable: String,
111        property: String,
112        pattern: ValidatedRegex,
113    },
114    PropertyIsNull {
115        variable: String,
116        property: String,
117    },
118    PropertyIsNotNull {
119        variable: String,
120        property: String,
121    },
122    PropertyIn {
123        variable: String,
124        property: String,
125        values: Vec<Value>,
126    },
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum ComparisonOp {
131    Lt,
132    Lte,
133    Gt,
134    Gte,
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum DatePart {
139    Year,
140    Month,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub enum ProjectionExpression {
145    Variable {
146        variable: String,
147    },
148    Property {
149        variable: String,
150        property: String,
151    },
152    Id {
153        variable: String,
154    },
155    RelationshipType {
156        variable: String,
157    },
158    Literal(Value),
159    Coalesce(Vec<ProjectionExpression>),
160    JsonValue {
161        expression: Box<ProjectionExpression>,
162        path: String,
163    },
164    Left {
165        expression: Box<ProjectionExpression>,
166        length: usize,
167    },
168    Lower(Box<ProjectionExpression>),
169    DatePart {
170        part: DatePart,
171        variable: String,
172        property: String,
173    },
174    DefaultIfNullOrEq {
175        variable: String,
176        property: String,
177        empty: Value,
178        default: Value,
179    },
180    DefaultIfNull {
181        variable: String,
182        property: String,
183        default: Value,
184    },
185    CasePropertyNotNullOrEq {
186        variable: String,
187        property: String,
188        empty: Value,
189        non_empty: Value,
190        null_or_empty: Value,
191    },
192    CasePropertyEqualsRank {
193        variable: String,
194        property: String,
195        branches: Vec<(Value, Value)>,
196        default: Value,
197    },
198    CaseLowerPropertyDefault {
199        variable: String,
200        property: String,
201        default: Value,
202    },
203    CaseCoalesceDifferenceFloorZero {
204        variable: String,
205        terms: Vec<CoalesceDifferenceProjectionTerm>,
206    },
207    Case {
208        operand: Option<Box<ProjectionExpression>>,
209        branches: Vec<(ProjectionExpression, ProjectionExpression)>,
210        otherwise: Option<Box<ProjectionExpression>>,
211    },
212    Binary {
213        left: Box<ProjectionExpression>,
214        op: ScalarBinaryOp,
215        right: Box<ProjectionExpression>,
216    },
217    Not(Box<ProjectionExpression>),
218    IsNull {
219        expression: Box<ProjectionExpression>,
220        negated: bool,
221    },
222    CaseEntitySearchRank(Box<CaseEntitySearchRankProjection>),
223    CaseColumnSearchRank(Box<CaseColumnSearchRankProjection>),
224    ColumnDefaultIfNullOrEq {
225        column: String,
226        property: String,
227        empty: Value,
228        default: Value,
229    },
230    ColumnValueDefaultIfNull {
231        column: String,
232        default: Value,
233    },
234    ColumnValueCasePropertyNotNullOrEq {
235        column: String,
236        empty: Value,
237        non_empty: Value,
238        null_or_empty: Value,
239    },
240    Column(String),
241    ColumnProperty {
242        column: String,
243        property: String,
244    },
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum ScalarBinaryOp {
249    Add,
250    Subtract,
251    Multiply,
252    Divide,
253    Remainder,
254    Eq,
255    NotEq,
256    Lt,
257    Lte,
258    Gt,
259    Gte,
260    Contains,
261    ListContains,
262    And,
263    Or,
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct CaseEntitySearchRankProjection {
268    pub variable: String,
269    pub name_property: String,
270    pub aliases_property: String,
271    pub raw_query: Value,
272    pub normalized_query: Value,
273    pub raw_input: Value,
274    pub exact_rank: Value,
275    pub alias_rank: Value,
276    pub fallback_rank: Value,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct CaseColumnSearchRankProjection {
281    pub column: String,
282    pub raw_query: Value,
283    pub normalized_query: Value,
284    pub exact_rank: Value,
285    pub contains_rank: Value,
286    pub fallback_rank: Value,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct CoalesceDifferenceProjectionTerm {
291    pub property: String,
292    pub default: Value,
293}
294
295impl ProjectionExpression {
296    /// Visits immediate scalar children in evaluation order, stopping on false.
297    pub fn all_children(&self, mut visit: impl FnMut(&ProjectionExpression) -> bool) -> bool {
298        match self {
299            Self::Case {
300                operand,
301                branches,
302                otherwise,
303            } => operand
304                .iter()
305                .map(Box::as_ref)
306                .chain(
307                    branches
308                        .iter()
309                        .flat_map(|(condition, result)| [condition, result]),
310                )
311                .chain(otherwise.iter().map(Box::as_ref))
312                .all(visit),
313            Self::Binary { left, right, .. } => visit(left) && visit(right),
314            Self::Not(expression)
315            | Self::IsNull { expression, .. }
316            | Self::Lower(expression)
317            | Self::Left { expression, .. }
318            | Self::JsonValue { expression, .. } => visit(expression),
319            Self::Coalesce(expressions) => expressions.iter().all(visit),
320            _ => true,
321        }
322    }
323}
324
325impl ProjectionExpression {
326    /// Mutates immediate scalar children in evaluation order; errors are not rolled back.
327    pub fn try_for_each_child_mut<E>(
328        &mut self,
329        mut visit: impl FnMut(&mut Self) -> Result<(), E>,
330    ) -> Result<(), E> {
331        match self {
332            Self::Case {
333                operand,
334                branches,
335                otherwise,
336            } => {
337                for expression in operand
338                    .iter_mut()
339                    .map(Box::as_mut)
340                    .chain(
341                        branches
342                            .iter_mut()
343                            .flat_map(|(condition, result)| [condition, result]),
344                    )
345                    .chain(otherwise.iter_mut().map(Box::as_mut))
346                {
347                    visit(expression)?;
348                }
349            }
350            Self::Binary { left, right, .. } => {
351                visit(left)?;
352                visit(right)?;
353            }
354            Self::Not(expression)
355            | Self::IsNull { expression, .. }
356            | Self::Lower(expression)
357            | Self::Left { expression, .. }
358            | Self::JsonValue { expression, .. } => visit(expression)?,
359            Self::Coalesce(expressions) => {
360                for expression in expressions {
361                    visit(expression)?;
362                }
363            }
364            _ => {}
365        }
366        Ok(())
367    }
368}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct SortItem {
372    pub key: SortKey,
373    pub direction: SortDirection,
374}
375
376#[derive(Debug, Clone, PartialEq, Eq)]
377pub enum SortKey {
378    Property { variable: String, property: String },
379    Id { variable: String },
380    Expression(ProjectionExpression),
381    Column(String),
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub enum SortDirection {
386    Asc,
387    Desc,
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393
394    #[test]
395    fn expression_ir_composes_boolean_and_scalar_nodes() {
396        let predicate = Predicate::ExpressionCompare {
397            expression: ProjectionExpression::Property {
398                variable: "memory".to_string(),
399                property: "importance".to_string(),
400            },
401            op: ComparisonOp::Gte,
402            value: ProjectionExpression::Literal(Value::Float(0.75)),
403        };
404
405        assert!(matches!(
406            predicate,
407            Predicate::ExpressionCompare {
408                op: ComparisonOp::Gte,
409                ..
410            }
411        ));
412    }
413}