Skip to main content

uni_query/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2026 Dragonscale Team
3
4//! Query execution layer for the Uni graph database.
5//!
6//! This crate provides OpenCypher query parsing, logical planning, and
7//! execution against Uni's object-store-backed property graph.
8//!
9//! # Modules
10//!
11//! - [`query`] — planner, executor, DataFusion integration, pushdown logic
12//! - [`types`] — public value types (`Value`, `Node`, `Edge`, `Path`, etc.)
13//!
14//! # Quick Start
15//!
16//! ```rust,ignore
17//! let executor = Executor::new(storage);
18//! let planner = QueryPlanner::new(schema);
19//! let plan = planner.plan(cypher_ast)?;
20//! let result = executor.execute_plan(plan, &params).await?;
21//! ```
22
23#![recursion_limit = "256"]
24
25pub mod query;
26pub mod types;
27
28pub use query::executor::core::{OperatorStats, ProfileOutput};
29pub use query::executor::procedure::{
30    ProcedureOutput, ProcedureParam, ProcedureRegistry, ProcedureValueType, RegisteredProcedure,
31};
32pub use query::executor::{CustomFunctionRegistry, CustomScalarFn, Executor, ResultNormalizer};
33pub use query::planner::{CostEstimates, ExplainOutput, IndexUsage, LogicalPlan, QueryPlanner};
34pub use types::{
35    Edge, ExecuteResult, FromValue, Node, Path, QueryCursor, QueryMetrics, QueryResult,
36    QueryWarning, Row, Value,
37};
38pub use uni_cypher::ast::{Query as CypherQuery, TimeTravelSpec};
39
40/// Validate that a query AST contains only read clauses.
41///
42/// Rejects any query that contains CREATE, MERGE, DELETE, SET, REMOVE,
43/// or schema commands.
44///
45/// # Errors
46///
47/// Returns `Err(message)` describing the first write clause found. Used to
48/// enforce read-only access for time-travel queries (`VERSION AS OF` /
49/// `TIMESTAMP AS OF`).
50pub fn validate_read_only(query: &CypherQuery) -> Result<(), String> {
51    use uni_cypher::ast::{Clause, Query, Statement};
52
53    fn check_statement(stmt: &Statement) -> Result<(), String> {
54        for clause in &stmt.clauses {
55            match clause {
56                Clause::Create(_)
57                | Clause::Merge(_)
58                | Clause::Delete(_)
59                | Clause::Set(_)
60                | Clause::Remove(_) => {
61                    return Err(
62                        "Write clauses (CREATE, MERGE, DELETE, SET, REMOVE) are not allowed \
63                         with VERSION AS OF / TIMESTAMP AS OF"
64                            .to_string(),
65                    );
66                }
67                _ => {}
68            }
69        }
70        Ok(())
71    }
72
73    fn check_query(q: &Query) -> Result<(), String> {
74        match q {
75            Query::Single(stmt) => check_statement(stmt),
76            Query::Union { left, right, .. } => {
77                check_query(left)?;
78                check_query(right)
79            }
80            Query::Explain(inner) => check_query(inner),
81            Query::TimeTravel { query, .. } => check_query(query),
82            Query::Schema(cmd) => {
83                use uni_cypher::ast::SchemaCommand;
84                match cmd.as_ref() {
85                    // Read-only schema commands are allowed
86                    SchemaCommand::ShowConstraints(_)
87                    | SchemaCommand::ShowIndexes(_)
88                    | SchemaCommand::ShowDatabase
89                    | SchemaCommand::ShowConfig
90                    | SchemaCommand::ShowStatistics => Ok(()),
91                    // All other schema commands mutate state
92                    _ => Err(
93                        "Mutating schema commands are not allowed in read-only context".to_string(),
94                    ),
95                }
96            }
97        }
98    }
99
100    check_query(query)
101}