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}