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 procedures_plugin;
26pub mod projection_store;
27pub mod query;
28pub mod types;
29
30pub use query::executor::core::{OperatorStats, ProfileOutput};
31pub use query::executor::procedure::{
32    ProcedureOutput, ProcedureParam, ProcedureRegistry, ProcedureValueType, RegisteredProcedure,
33};
34pub use query::executor::{CustomFunctionRegistry, CustomScalarFn, Executor, ResultNormalizer};
35// M8.6: session-scoped plugin registry plumbing. Host crates wrap their
36// per-query execution paths with `scoped_with_session_plugin_registry`;
37// the executor consults `current_session_plugin_registry` at the UDF /
38// procedure / Locy-aggregate resolution sites.
39pub use query::df_udfs_plugin::{
40    CURRENT_PRINCIPAL, SESSION_PLUGIN_REGISTRY, current_principal, current_session_plugin_registry,
41    maybe_scope_with_principal, scoped_with_principal, scoped_with_session_context,
42    scoped_with_session_plugin_registry,
43};
44pub use query::planner::{
45    CostEstimates, ExplainOutput, ForkIndexLookup, FusionKind, IndexUsage, LogicalPlan,
46    QueryPlanner, rewrite_for_fork_fusion,
47};
48pub use types::{
49    Edge, ExecuteResult, FromValue, Node, Path, QueryCursor, QueryMetrics, QueryResult,
50    QueryWarning, Row, Value,
51};
52pub use uni_cypher::ast::{Query as CypherQuery, TimeTravelSpec};
53
54/// Validate that a query AST contains only read clauses.
55///
56/// Rejects any query that contains CREATE, MERGE, DELETE, SET, REMOVE,
57/// or schema commands.
58///
59/// # Errors
60///
61/// Returns `Err(message)` describing the first write clause found. Used to
62/// enforce read-only access for time-travel queries (`VERSION AS OF` /
63/// `TIMESTAMP AS OF`).
64pub fn validate_read_only(query: &CypherQuery) -> Result<(), String> {
65    use uni_cypher::ast::{Clause, Query, Statement};
66
67    fn check_statement(stmt: &Statement) -> Result<(), String> {
68        for clause in &stmt.clauses {
69            match clause {
70                Clause::Create(_)
71                | Clause::Merge(_)
72                | Clause::Delete(_)
73                | Clause::Set(_)
74                | Clause::Remove(_) => {
75                    return Err(
76                        "Write clauses (CREATE, MERGE, DELETE, SET, REMOVE) are not allowed \
77                         with VERSION AS OF / TIMESTAMP AS OF"
78                            .to_string(),
79                    );
80                }
81                _ => {}
82            }
83        }
84        Ok(())
85    }
86
87    fn check_query(q: &Query) -> Result<(), String> {
88        match q {
89            Query::Single(stmt) => check_statement(stmt),
90            Query::Union { left, right, .. } => {
91                check_query(left)?;
92                check_query(right)
93            }
94            Query::Explain(inner) => check_query(inner),
95            Query::TimeTravel { query, .. } => check_query(query),
96            Query::Schema(cmd) => {
97                use uni_cypher::ast::SchemaCommand;
98                match cmd.as_ref() {
99                    // Read-only schema commands are allowed
100                    SchemaCommand::ShowConstraints(_)
101                    | SchemaCommand::ShowIndexes(_)
102                    | SchemaCommand::ShowDatabase
103                    | SchemaCommand::ShowConfig
104                    | SchemaCommand::ShowStatistics => Ok(()),
105                    // All other schema commands mutate state
106                    _ => Err(
107                        "Mutating schema commands are not allowed in read-only context".to_string(),
108                    ),
109                }
110            }
111        }
112    }
113
114    check_query(query)
115}