Skip to main content

lora_database/
explain.rs

1//! Public result types for `Database::explain` and `Database::profile`.
2//!
3//! These are deliberately separate from the `QueryResult` family used by
4//! `execute()` so the language bindings can surface plan / profile
5//! payloads without trying to fit them into the row-shaped result type.
6//!
7//! `QueryPlan` is what `explain()` returns; the query is parsed,
8//! analyzed, and compiled but never executed. `QueryProfile` is what
9//! `profile()` returns; the query is fully executed (including any
10//! mutations) and the plan tree is decorated with coarse runtime
11//! metrics.
12
13use std::collections::BTreeMap;
14
15use lora_compiler::PlanTree;
16
17/// Whether a compiled plan is read-only or potentially mutates the
18/// graph. Mirrors `lora_executor::StreamShape` but stays a stable
19/// public surface for bindings that don't pull in the executor crate.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PlanShape {
22    ReadOnly,
23    Mutating,
24}
25
26impl PlanShape {
27    pub fn is_mutating(self) -> bool {
28        matches!(self, PlanShape::Mutating)
29    }
30
31    pub fn as_str(self) -> &'static str {
32        match self {
33            PlanShape::ReadOnly => "readOnly",
34            PlanShape::Mutating => "mutating",
35        }
36    }
37}
38
39impl From<lora_executor::StreamShape> for PlanShape {
40    fn from(value: lora_executor::StreamShape) -> Self {
41        match value {
42            lora_executor::StreamShape::ReadOnly => PlanShape::ReadOnly,
43            lora_executor::StreamShape::Mutating => PlanShape::Mutating,
44        }
45    }
46}
47
48/// Result of `Database::explain`.
49#[derive(Debug, Clone)]
50pub struct QueryPlan {
51    /// The exact query text the caller submitted.
52    pub query: String,
53    /// Operator tree, leaf-most first under each node.
54    pub tree: PlanTree,
55    /// Whether running this plan would (potentially) mutate the graph.
56    pub shape: PlanShape,
57    /// Result column names in projection order. Empty for plans
58    /// without a top-level projection (e.g. plans that only mutate).
59    pub result_columns: Vec<String>,
60}
61
62/// Coarse-grained per-query runtime metrics. v1 reports totals, not
63/// per-operator timings; per-operator instrumentation is reserved
64/// for a future phase that will not change the public surface.
65#[derive(Debug, Clone, Default, PartialEq, Eq)]
66pub struct ProfileMetrics {
67    /// Wall-clock time spent inside the executor for this query.
68    pub total_elapsed_ns: u64,
69    /// Number of rows produced before result-format projection.
70    pub total_rows: u64,
71    /// Whether at least one mutating operator ran.
72    pub mutated: bool,
73    /// Reserved for future operator-level metrics. Present today as
74    /// an empty map so consumers can pattern-match on the field
75    /// without breaking when v2 starts populating it.
76    pub per_operator: BTreeMap<usize, OperatorMetrics>,
77}
78
79/// Per-operator metrics. Reserved for a future phase; today no
80/// operator populates this.
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
82pub struct OperatorMetrics {
83    pub rows: u64,
84    pub db_hits: u64,
85    pub elapsed_ns: u64,
86    pub next_calls: u64,
87}
88
89/// Result of `Database::profile`.
90#[derive(Debug, Clone)]
91pub struct QueryProfile {
92    /// The plan that was profiled. Same shape as `QueryPlan` from
93    /// `explain()`.
94    pub plan: QueryPlan,
95    /// Runtime metrics gathered during execution.
96    pub metrics: ProfileMetrics,
97}