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