linkrs_query/context/query_context.rs
1//! Query context
2//!
3//! Manage the context information throughout the entire lifecycle of queries, from parsing and validation to planning and execution.
4//!
5//! ## Creation
6//!
7//! Use [`QueryContext::new`] for simple cases or [`QueryContext::builder`] for complex configuration:
8//!
9//! ```rust,ignore
10//! use linkrs::query::context::{QueryContext, QueryRequestContext};
11//! use std::sync::Arc;
12//!
13//! // Simple creation
14//! let rctx = Arc::new(QueryRequestContext::new("MATCH (n) RETURN n".to_string()));
15//! let ctx = QueryContext::new(rctx);
16//!
17//! // With builder (for complex configuration)
18//! let ctx = QueryContext::builder(rctx)
19//! .with_space_info(space_info)
20//! .with_arena()
21//! .build();
22//! ```
23//!
24//! ## Arena Allocation
25//!
26//! For high-performance query execution with many temporary allocations,
27//! enable arena allocation via the builder's `with_arena()` method. This is beneficial for:
28//!
29//! - Complex queries with many intermediate results
30//! - Expression evaluation with temporary values
31//! - Graph traversal with path accumulation
32
33use std::sync::Arc;
34
35use crate::executor::streaming::query_registry::CancelToken;
36use crate::executor::streaming::transaction_scope::CancelReason;
37use linkrs_core::types::{CharsetInfo, SpaceInfo, Timestamp};
38use linkrs_core::IdGenerator;
39
40use super::QueryRequestContext;
41
42/// Query context
43///
44/// The context for each query request is created whenever the query request is received by the query engine.
45/// This context object is visible to the parser, planner, optimizer, and executor.
46///
47/// # Responsibilities
48///
49/// - Query request context (session information, request parameters)
50/// - Request-scoped cancellation token (shared with the execution runtime and
51/// the process-level query registry — one token for all cancellation)
52/// - ID generation for query execution
53/// - Space information management (space info, character set)
54/// - Optional arena allocator for high-performance temporary allocations
55///
56/// # Creation
57///
58/// Use [`QueryContext::new`] for simple cases or [`QueryContext::builder`] for complex configuration.
59pub struct QueryContext {
60 /// Query request context
61 rctx: Arc<QueryRequestContext>,
62 /// Request-scoped cancellation token.
63 ///
64 /// The execution pipeline threads this token into the query registry and
65 /// the execution runtime (`instantiate_plan`), so `mark_killed`, KILL
66 /// QUERY, and runtime cancel all flip the same underlying state.
67 cancel_token: CancelToken,
68 /// ID Generator for query execution
69 id_gen: IdGenerator,
70 /// Current space information
71 space_info: Option<SpaceInfo>,
72 /// Character set information
73 charset_info: Option<Box<CharsetInfo>>,
74 /// MVCC snapshot timestamp for consistent reads within explicit
75 /// transactions. `None` = auto-commit single statement (current-version
76 /// reads).
77 snapshot_ts: Option<Timestamp>,
78 /// Transaction isolation level injected by the API layer for queries
79 /// running inside an explicit transaction. `None` = auto-commit
80 /// statement-level snapshot semantics. Execution-time knob: the runtime
81 /// and operators can consult it instead of only the storage layer.
82 isolation_level: Option<linkrs_core::types::TransactionIsolationLevel>,
83 /// Whether arena allocation is enabled for this query's execution. The
84 /// concrete arena is owned by the execution context, not by this struct, so
85 /// the context stays `Send + Sync`.
86 arena_enabled: bool,
87 /// Retained password-history depth applied by user-management operations
88 /// in this query. Injected by the API layer from server configuration;
89 /// zero clears history on rotation.
90 password_history_depth: usize,
91}
92
93/// Builder-supplied context parameters, grouped so the internal constructor
94/// stays within a manageable arity.
95pub(super) struct ContextParams {
96 pub cancel_token: CancelToken,
97 pub id_gen: IdGenerator,
98 pub space_info: Option<SpaceInfo>,
99 pub charset_info: Option<Box<CharsetInfo>>,
100 pub snapshot_ts: Option<Timestamp>,
101 pub isolation_level: Option<linkrs_core::types::TransactionIsolationLevel>,
102 pub arena_enabled: bool,
103 pub password_history_depth: usize,
104}
105
106impl QueryContext {
107 /// Create a new query context with default configuration.
108 ///
109 /// For complex configuration (arena allocation, custom ID generator, etc.),
110 /// use [`QueryContext::builder`] instead.
111 pub fn new(rctx: Arc<QueryRequestContext>) -> Self {
112 Self {
113 rctx,
114 cancel_token: CancelToken::new(),
115 id_gen: IdGenerator::new(0),
116 space_info: None,
117 charset_info: None,
118 snapshot_ts: None,
119 isolation_level: None,
120 arena_enabled: false,
121 password_history_depth: 0,
122 }
123 }
124
125 /// Create a builder for complex configuration.
126 ///
127 /// # Example
128 ///
129 /// ```rust,ignore
130 /// let ctx = QueryContext::builder(rctx)
131 /// .with_space_info(space_info)
132 /// .with_charset_info(charset_info)
133 /// .with_arena()
134 /// .build();
135 /// ```
136 pub fn builder(rctx: Arc<QueryRequestContext>) -> super::QueryContextBuilder {
137 super::QueryContextBuilder::new(rctx)
138 }
139
140 /// Internal constructor for QueryContextBuilder.
141 /// Only visible within the query::context module.
142 pub(super) fn from_builder(rctx: Arc<QueryRequestContext>, params: ContextParams) -> Self {
143 Self {
144 rctx,
145 cancel_token: params.cancel_token,
146 id_gen: params.id_gen,
147 space_info: params.space_info,
148 charset_info: params.charset_info,
149 snapshot_ts: params.snapshot_ts,
150 isolation_level: params.isolation_level,
151 arena_enabled: params.arena_enabled,
152 password_history_depth: params.password_history_depth,
153 }
154 }
155
156 /// Obtain the context of the query request.
157 pub fn request_context(&self) -> &QueryRequestContext {
158 &self.rctx
159 }
160
161 /// The Arc reference that provides the context for the query request.
162 pub fn request_context_arc(&self) -> Arc<QueryRequestContext> {
163 self.rctx.clone()
164 }
165
166 /// Obtain the context of the query request (compatible with old code)
167 pub fn rctx(&self) -> &QueryRequestContext {
168 &self.rctx
169 }
170
171 /// The request-scoped cancellation token.
172 ///
173 /// Threaded into the execution runtime and query registry at instantiation
174 /// so all cancellation paths share one source of truth.
175 pub fn cancel_token(&self) -> CancelToken {
176 self.cancel_token.clone()
177 }
178
179 /// Obtaining character set information
180 pub fn charset_info(&self) -> Option<&CharsetInfo> {
181 self.charset_info.as_ref().map(|ci| ci.as_ref())
182 }
183
184 /// Setting character set information
185 pub fn set_charset_info(&mut self, charset_info: CharsetInfo) {
186 self.charset_info = Some(Box::new(charset_info));
187 }
188
189 /// The MVCC snapshot timestamp for consistent reads within explicit
190 /// transactions.
191 ///
192 /// `None` means auto-commit single-statement execution, which reads the
193 /// current version of the data.
194 pub fn snapshot_ts(&self) -> Option<Timestamp> {
195 self.snapshot_ts
196 }
197
198 /// The transaction isolation level for this execution, when running
199 /// inside an explicit transaction.
200 pub fn isolation_level(&self) -> Option<linkrs_core::types::TransactionIsolationLevel> {
201 self.isolation_level
202 }
203
204 /// The retained password-history depth for user-management operations
205 /// planned under this context.
206 pub fn password_history_depth(&self) -> usize {
207 self.password_history_depth
208 }
209
210 /// The authenticated user that issued this query, when the request came
211 /// from an authenticated session.
212 ///
213 /// Self-service password changes omit the username in the statement; the
214 /// planner backfills it from here so the execution chain targets the
215 /// caller instead of failing on an empty username.
216 pub fn authenticated_user(&self) -> Option<&str> {
217 self.rctx.user_name.as_deref()
218 }
219
220 /// Generate an ID.
221 pub fn gen_id(&self) -> i64 {
222 self.id_gen.id()
223 }
224
225 /// Retrieve the current ID value (without incrementing it).
226 pub fn current_id(&self) -> i64 {
227 self.id_gen.current_value()
228 }
229
230 /// Obtain the current spatial information
231 pub fn space_info(&self) -> Option<&SpaceInfo> {
232 self.space_info.as_ref()
233 }
234
235 /// Set the current space information
236 pub fn set_space_info(&mut self, space_info: SpaceInfo) {
237 self.space_info = Some(space_info);
238 }
239
240 /// Obtain the ID of the current space.
241 pub fn space_id(&self) -> Option<u64> {
242 self.space_info.as_ref().map(|s| s.space_id)
243 }
244
245 /// Get the name of the current space.
246 pub fn space_name(&self) -> Option<String> {
247 self.space_info.as_ref().map(|s| s.space_name.clone())
248 }
249
250 /// Mark this query as killed.
251 ///
252 /// Cancels the request-scoped token shared with the execution runtime and
253 /// the query registry, so running operators abort at the next cooperative
254 /// cancellation check and `KILL QUERY` observability reflects the state.
255 pub fn mark_killed(&self) {
256 self.cancel_token.cancel(CancelReason::UserKill);
257 }
258
259 /// Check whether this query has been killed/cancelled.
260 pub fn is_killed(&self) -> bool {
261 self.cancel_token.is_cancelled()
262 }
263
264 /// Check whether the parameters exist.
265 pub fn exist_parameter(&self, param: &str) -> bool {
266 self.rctx.get_parameter(param).is_some()
267 }
268
269 /// Obtain the query string
270 pub fn query(&self) -> &str {
271 &self.rctx.query
272 }
273
274 /// Obtain parameters
275 pub fn parameters(&self) -> &std::collections::HashMap<String, linkrs_core::Value> {
276 &self.rctx.parameters
277 }
278
279 /// Reset the query context
280 pub fn reset(&mut self) {
281 self.cancel_token.clear();
282 self.id_gen.reset(0);
283 self.space_info = None;
284 self.charset_info = None;
285 self.snapshot_ts = None;
286 self.isolation_level = None;
287 log::info!("Query context has been reset");
288 }
289
290 /// Check if arena allocation is enabled for this query's execution.
291 pub fn has_arena(&self) -> bool {
292 self.arena_enabled
293 }
294
295 // Note: resource_context() and space_context() methods have been removed
296 // as part of the optimization to inline these contexts into QueryContext.
297 // Use the direct accessor methods instead:
298 // - gen_id(), current_id() for resource operations
299 // - space_info(), space_id(), space_name(), charset_info() for space operations
300}
301
302impl std::fmt::Debug for QueryContext {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct("QueryContext")
305 .field("rctx", &self.rctx)
306 .field("space_id", &self.space_id())
307 .field("snapshot_ts", &self.snapshot_ts)
308 .field("isolation_level", &self.isolation_level)
309 .field("killed", &self.is_killed())
310 .field("has_arena", &self.arena_enabled)
311 .finish()
312 }
313}
314
315impl Default for QueryContext {
316 fn default() -> Self {
317 Self::new(Arc::new(QueryRequestContext::default()))
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn query_context_is_send_and_sync() {
327 fn assert_send_sync<T: Send + Sync>() {}
328 assert_send_sync::<QueryContext>();
329 assert_send_sync::<Arc<QueryContext>>();
330 }
331
332 #[test]
333 fn mark_killed_cancels_shared_token() {
334 let rctx = Arc::new(QueryRequestContext::new("MATCH (n) RETURN n".to_string()));
335 let ctx = QueryContext::new(rctx);
336 assert!(!ctx.is_killed());
337
338 // A clone of the token is what the pipeline hands to the runtime.
339 let runtime_token = ctx.cancel_token();
340 assert!(!runtime_token.is_cancelled());
341
342 ctx.mark_killed();
343
344 assert!(ctx.is_killed());
345 assert!(runtime_token.is_cancelled());
346 assert_eq!(runtime_token.reason(), Some(CancelReason::UserKill));
347 }
348
349 #[test]
350 fn runtime_adopts_context_token() {
351 use crate::executor::streaming::runtime::ExecutionRuntime;
352 let rctx = Arc::new(QueryRequestContext::new("MATCH (n) RETURN n".to_string()));
353 let ctx = QueryContext::new(rctx);
354
355 let runtime = ExecutionRuntime::default_budget();
356 runtime.set_cancel_token(ctx.cancel_token());
357 assert!(!runtime.is_cancelled());
358
359 ctx.mark_killed();
360 assert!(runtime.is_cancelled());
361 assert!(runtime.ensure_not_cancelled().is_err());
362 }
363
364 #[test]
365 fn reset_clears_cancellation() {
366 let rctx = Arc::new(QueryRequestContext::new("MATCH (n) RETURN n".to_string()));
367 let mut ctx = QueryContext::new(rctx);
368 ctx.mark_killed();
369 assert!(ctx.is_killed());
370
371 ctx.reset();
372 assert!(!ctx.is_killed());
373 }
374}