Skip to main content

plexus_serde/
types.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub struct Version {
3    pub major: u32,
4    pub minor: u32,
5    pub patch: u32,
6    pub producer: String,
7}
8
9pub const PLAN_FORMAT_MAJOR: u32 = 0;
10pub const PLAN_FORMAT_MINOR: u32 = 3;
11pub const PLAN_FORMAT_PATCH: u32 = 0;
12
13pub fn current_plan_version(producer: impl Into<String>) -> Version {
14    Version {
15        major: PLAN_FORMAT_MAJOR,
16        minor: PLAN_FORMAT_MINOR,
17        patch: PLAN_FORMAT_PATCH,
18        producer: producer.into(),
19    }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ColKind {
24    Node,
25    Rel,
26    Value,
27    Bool,
28    Int64,
29    Float64,
30    String,
31    Null,
32    List,
33    Map,
34    Path,
35    Graph,
36    Vector,
37}
38
39/// Structured logical type for a plan column.
40/// Replaces the legacy `Option<String>` advisory field in `ColDef` as of plan format 0.3.0.
41#[derive(Debug, Clone, PartialEq, Eq, Default)]
42pub enum LogicalType {
43    #[default]
44    Unknown,
45    Bool,
46    Int64,
47    Float64,
48    String,
49    Null,
50    List,
51    Map,
52    NodeRef,
53    RelRef,
54    Path,
55    Vector {
56        element_type: std::string::String,
57        dimension: Option<u32>,
58    },
59}
60
61impl LogicalType {
62    /// Parse the legacy string representation used in pre-0.3 plans.
63    pub fn from_legacy_str(s: &str) -> Self {
64        match s {
65            "bool" => Self::Bool,
66            "int64" => Self::Int64,
67            "float64" => Self::Float64,
68            "string" => Self::String,
69            "null" => Self::Null,
70            "map" => Self::Map,
71            "node" | "node_ref" => Self::NodeRef,
72            "rel" | "rel_ref" => Self::RelRef,
73            "path" => Self::Path,
74            _ if s.starts_with("list") => Self::List,
75            _ if s.starts_with("vector") => {
76                // Legacy format: "vector<element_type,dimension>" or "vector<element_type>"
77                let inner = s
78                    .strip_prefix("vector<")
79                    .and_then(|r| r.strip_suffix('>'))
80                    .unwrap_or("");
81                let mut parts = inner.splitn(2, ',');
82                let element_type = parts.next().unwrap_or("float32").to_string();
83                let dimension = parts.next().and_then(|d| d.parse().ok());
84                Self::Vector {
85                    element_type,
86                    dimension,
87                }
88            }
89            _ => Self::Unknown,
90        }
91    }
92
93    /// Serialize to the legacy string representation for cross-version compatibility.
94    pub fn as_legacy_str(&self) -> Option<&str> {
95        match self {
96            Self::Unknown => None,
97            Self::Bool => Some("bool"),
98            Self::Int64 => Some("int64"),
99            Self::Float64 => Some("float64"),
100            Self::String => Some("string"),
101            Self::Null => Some("null"),
102            Self::List => Some("list<value>"),
103            Self::Map => Some("map"),
104            Self::NodeRef => Some("node_ref"),
105            Self::RelRef => Some("rel_ref"),
106            Self::Path => Some("path"),
107            Self::Vector { .. } => Some("vector"),
108        }
109    }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct ColDef {
114    pub name: String,
115    pub kind: ColKind,
116    pub logical_type: LogicalType,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum CmpOp {
121    Eq,
122    Ne,
123    Lt,
124    Gt,
125    Le,
126    Ge,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum AggFn {
131    CountStar,
132    Count,
133    Sum,
134    Avg,
135    Min,
136    Max,
137    Collect,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum ArithOp {
142    Add,
143    Sub,
144    Mul,
145    Div,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum VectorMetric {
150    Cosine,
151    L2,
152    DotProduct,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum ExpandDir {
157    Out,
158    In,
159    Both,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum SortDir {
164    Asc,
165    Desc,
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub enum Expr {
170    ColRef {
171        idx: u32,
172    },
173    PropAccess {
174        col: u32,
175        prop: String,
176    },
177    IntLiteral(i64),
178    FloatLiteral(f64),
179    BoolLiteral(bool),
180    StringLiteral(String),
181    NullLiteral,
182    Cmp {
183        op: CmpOp,
184        lhs: Box<Expr>,
185        rhs: Box<Expr>,
186    },
187    And {
188        lhs: Box<Expr>,
189        rhs: Box<Expr>,
190    },
191    Or {
192        lhs: Box<Expr>,
193        rhs: Box<Expr>,
194    },
195    Not {
196        expr: Box<Expr>,
197    },
198    IsNull {
199        expr: Box<Expr>,
200    },
201    IsNotNull {
202        expr: Box<Expr>,
203    },
204    StartsWith {
205        expr: Box<Expr>,
206        pattern: String,
207    },
208    EndsWith {
209        expr: Box<Expr>,
210        pattern: String,
211    },
212    Contains {
213        expr: Box<Expr>,
214        pattern: String,
215    },
216    In {
217        expr: Box<Expr>,
218        items: Vec<Expr>,
219    },
220    ListLiteral {
221        items: Vec<Expr>,
222    },
223    MapLiteral {
224        entries: Vec<(String, Expr)>,
225    },
226    Exists {
227        expr: Box<Expr>,
228    },
229    ListComprehension {
230        list: Box<Expr>,
231        var: String,
232        predicate: Option<Box<Expr>>,
233        map: Box<Expr>,
234    },
235    Agg {
236        fn_: AggFn,
237        expr: Option<Box<Expr>>,
238    },
239    Arith {
240        op: ArithOp,
241        lhs: Box<Expr>,
242        rhs: Box<Expr>,
243    },
244    Param {
245        name: String,
246        expected_type: Option<String>,
247    },
248    Case {
249        arms: Vec<(Expr, Expr)>,
250        else_expr: Option<Box<Expr>>,
251    },
252    VectorSimilarity {
253        metric: VectorMetric,
254        lhs: Box<Expr>,
255        rhs: Box<Expr>,
256    },
257}
258
259#[derive(Debug, Clone, PartialEq)]
260pub enum Op {
261    ScanNodes {
262        labels: Vec<String>,
263        schema: Vec<ColDef>,
264        must_labels: Vec<String>,
265        forbidden_labels: Vec<String>,
266        est_rows: i64,
267        selectivity: f64,
268        graph_ref: Option<String>,
269    },
270    ScanRels {
271        types: Vec<String>,
272        schema: Vec<ColDef>,
273        src_labels: Vec<String>,
274        dst_labels: Vec<String>,
275        est_rows: i64,
276        selectivity: f64,
277    },
278    Expand {
279        input: u32,
280        src_col: u32,
281        types: Vec<String>,
282        dir: ExpandDir,
283        schema: Vec<ColDef>,
284        src_var: String,
285        rel_var: String,
286        dst_var: String,
287        legal_src_labels: Vec<String>,
288        legal_dst_labels: Vec<String>,
289        est_degree: f64,
290        graph_ref: Option<String>,
291    },
292    OptionalExpand {
293        input: u32,
294        src_col: u32,
295        types: Vec<String>,
296        dir: ExpandDir,
297        schema: Vec<ColDef>,
298        src_var: String,
299        rel_var: String,
300        dst_var: String,
301        legal_src_labels: Vec<String>,
302        legal_dst_labels: Vec<String>,
303        graph_ref: Option<String>,
304    },
305    SemiExpand {
306        input: u32,
307        src_col: u32,
308        types: Vec<String>,
309        dir: ExpandDir,
310        schema: Vec<ColDef>,
311        legal_src_labels: Vec<String>,
312        legal_dst_labels: Vec<String>,
313    },
314    ExpandVarLen {
315        input: u32,
316        src_col: u32,
317        types: Vec<String>,
318        dir: ExpandDir,
319        min_hops: i32,
320        max_hops: i32,
321        schema: Vec<ColDef>,
322        src_var: String,
323        path_var: String,
324        dst_var: String,
325        graph_ref: Option<String>,
326    },
327    Filter {
328        input: u32,
329        predicate: Expr,
330    },
331    BlockMarker {
332        input: u32,
333        block_id: i32,
334        branch_id: i32,
335    },
336    Project {
337        input: u32,
338        exprs: Vec<Expr>,
339        schema: Vec<ColDef>,
340    },
341    Aggregate {
342        input: u32,
343        keys: Vec<u32>,
344        aggs: Vec<Expr>,
345        schema: Vec<ColDef>,
346    },
347    Sort {
348        input: u32,
349        keys: Vec<u32>,
350        dirs: Vec<SortDir>,
351    },
352    Limit {
353        input: u32,
354        count: i64,
355        skip: i64,
356        /// Opaque engine-internal cursor bytes from a prior execution.
357        /// When `Some`, the engine resumes from this cursor instead of using `skip`.
358        cursor: Option<Vec<u8>>,
359        /// When `true`, the engine should emit a continuation cursor in the
360        /// execution result if more rows exist beyond `count`.
361        emit_cursor: bool,
362    },
363    Unwind {
364        input: u32,
365        list_expr: Expr,
366        out_var: String,
367        schema: Vec<ColDef>,
368    },
369    PathConstruct {
370        input: u32,
371        rel_cols: Vec<u32>,
372        schema: Vec<ColDef>,
373    },
374    Union {
375        lhs: u32,
376        rhs: u32,
377        all: bool,
378        schema: Vec<ColDef>,
379    },
380    CreateNode {
381        input: u32,
382        labels: Vec<String>,
383        props: Expr,
384        schema: Vec<ColDef>,
385        out_var: String,
386    },
387    CreateRel {
388        input: u32,
389        src_col: i32,
390        dst_col: i32,
391        rel_type: String,
392        props: Expr,
393        schema: Vec<ColDef>,
394        out_var: String,
395    },
396    Merge {
397        input: u32,
398        pattern: Expr,
399        on_create_props: Expr,
400        on_match_props: Expr,
401        schema: Vec<ColDef>,
402    },
403    Delete {
404        input: u32,
405        target_col: i32,
406        detach: bool,
407        schema: Vec<ColDef>,
408    },
409    SetProperty {
410        input: u32,
411        target_col: i32,
412        key: String,
413        value_expr: Expr,
414        schema: Vec<ColDef>,
415    },
416    RemoveProperty {
417        input: u32,
418        target_col: i32,
419        key: String,
420        schema: Vec<ColDef>,
421    },
422    VectorScan {
423        input: u32,
424        collection: String,
425        query_vector: Expr,
426        metric: VectorMetric,
427        top_k: u32,
428        approx_hint: bool,
429        schema: Vec<ColDef>,
430    },
431    Rerank {
432        input: u32,
433        score_expr: Expr,
434        top_k: u32,
435        schema: Vec<ColDef>,
436    },
437    Return {
438        input: u32,
439    },
440    /// Zero-input source op that emits exactly one empty row.
441    /// Used as the seed for standalone CREATE/INSERT without a preceding MATCH.
442    ConstRow,
443}
444
445#[derive(Debug, Clone, PartialEq)]
446pub struct Plan {
447    pub version: Version,
448    pub ops: Vec<Op>,
449    pub root_op: u32,
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq)]
453pub struct CapabilitySemver {
454    pub major: u32,
455    pub minor: u32,
456    pub patch: u32,
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
460pub struct CapabilityVersionRange {
461    pub min_supported: CapabilitySemver,
462    pub max_supported: CapabilitySemver,
463}
464
465#[derive(Debug, Clone, Copy, PartialEq, Eq)]
466pub enum CapabilityOrderingContract {
467    EngineDefinedStable,
468    StablePassThrough,
469    FanOutDeterministicPerInput,
470    DeterministicSortStableTies,
471    UnspecifiedWithoutSort,
472    StableConcatOrUnspecifiedDistinct,
473    ScoreDescStableTies,
474}
475
476#[derive(Debug, Clone, PartialEq, Eq)]
477pub struct OpOrderingDecl {
478    pub op: String,
479    pub contract: CapabilityOrderingContract,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq)]
483pub struct EngineCapabilityDecl {
484    pub version_range: CapabilityVersionRange,
485    pub supported_ops: Vec<String>,
486    pub supported_exprs: Vec<String>,
487    pub op_ordering: Vec<OpOrderingDecl>,
488    pub supports_graph_ref: bool,
489    pub supports_multi_graph: bool,
490    pub supports_graph_params: bool,
491}
492
493#[derive(Debug)]
494pub enum SerdeError {
495    InvalidFlatbuffer(flatbuffers::InvalidFlatbuffer),
496    MissingField(&'static str),
497    Unsupported(&'static str),
498}
499
500impl core::fmt::Display for SerdeError {
501    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
502        match self {
503            Self::InvalidFlatbuffer(e) => write!(f, "invalid flatbuffer: {e}"),
504            Self::MissingField(x) => write!(f, "missing required field: {x}"),
505            Self::Unsupported(x) => write!(f, "unsupported payload: {x}"),
506        }
507    }
508}
509
510impl std::error::Error for SerdeError {}
511
512impl From<flatbuffers::InvalidFlatbuffer> for SerdeError {
513    fn from(value: flatbuffers::InvalidFlatbuffer) -> Self {
514        Self::InvalidFlatbuffer(value)
515    }
516}