linkrs_query/executor/streaming/
query_registry.rs1use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::Arc;
10use std::time::Instant;
11
12use dashmap::DashMap;
13
14use super::transaction_scope::CancelReason;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct QueryId(pub u64);
23
24impl std::fmt::Display for QueryId {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "Q{}", self.0)
27 }
28}
29
30impl QueryId {
31 pub const fn new(value: u64) -> Self {
32 Self(value)
33 }
34
35 pub fn is_zero(&self) -> bool {
36 self.0 == 0
37 }
38
39 pub fn as_u64(&self) -> u64 {
40 self.0
41 }
42
43 pub fn as_i64(&self) -> i64 {
44 self.0 as i64
45 }
46}
47
48#[derive(Debug, Clone)]
52pub struct QueryMetadata {
53 pub query_id: QueryId,
54 pub session_id: Option<i64>,
55 pub user_name: Option<String>,
56 pub space_name: Option<String>,
57 pub query_text: Option<String>,
58 pub start_time: Instant,
59}
60
61#[derive(Debug)]
71pub struct QueryRegistry {
72 next_id: AtomicU64,
73 active: DashMap<QueryId, QueryEntry>,
74}
75
76#[derive(Debug)]
77struct QueryEntry {
78 _metadata: QueryMetadata,
79 cancel_reason: Arc<parking_lot::Mutex<Option<CancelReason>>>,
80}
81
82impl QueryRegistry {
83 pub fn new() -> Self {
84 Self {
85 next_id: AtomicU64::new(1),
86 active: DashMap::new(),
87 }
88 }
89
90 pub fn register(&self, metadata: QueryMetadata) -> (QueryId, CancelToken) {
95 self.register_with_token(QueryId(0), metadata, None)
96 }
97
98 pub fn register_with_id(
105 &self,
106 requested: QueryId,
107 metadata: QueryMetadata,
108 ) -> (QueryId, CancelToken) {
109 self.register_with_token(requested, metadata, None)
110 }
111
112 pub fn register_with_token(
120 &self,
121 requested: QueryId,
122 metadata: QueryMetadata,
123 external_token: Option<CancelToken>,
124 ) -> (QueryId, CancelToken) {
125 let id = if requested.is_zero() || self.active.contains_key(&requested) {
126 self.allocate_id()
127 } else {
128 requested
129 };
130 let token = external_token.unwrap_or_default();
131 let reason = token.0.clone();
132
133 self.active.insert(
134 id,
135 QueryEntry {
136 _metadata: QueryMetadata {
137 query_id: id,
138 ..metadata
139 },
140 cancel_reason: reason,
141 },
142 );
143
144 (id, token)
145 }
146
147 pub fn unregister(&self, query_id: QueryId) -> bool {
152 self.active.remove(&query_id).is_some()
153 }
154
155 pub fn cancel(&self, query_id: QueryId, reason: CancelReason) -> Option<CancelReason> {
160 if let Some(entry) = self.active.get(&query_id) {
161 let mut slot = entry.cancel_reason.lock();
162 if slot.is_some() {
163 None } else {
165 *slot = Some(reason);
166 slot.clone()
167 }
168 } else {
169 None
170 }
171 }
172
173 pub fn cancel_all(&self, reason: CancelReason) -> Vec<QueryId> {
175 let ids: Vec<QueryId> = self.active.iter().map(|e| *e.key()).collect();
176 for id in &ids {
177 self.cancel(*id, reason.clone());
178 }
179 ids
180 }
181
182 pub fn cancellation_reason(&self, query_id: QueryId) -> Option<CancelReason> {
184 self.active
185 .get(&query_id)
186 .and_then(|e| e.cancel_reason.lock().clone())
187 }
188
189 pub fn active_count(&self) -> usize {
191 self.active.len()
192 }
193
194 pub fn active_queries(&self) -> Vec<QueryId> {
196 self.active.iter().map(|e| *e.key()).collect()
197 }
198
199 fn allocate_id(&self) -> QueryId {
200 loop {
201 let raw = self.next_id.fetch_add(1, Ordering::Relaxed);
202 if raw != 0 {
203 return QueryId(raw);
204 }
205 }
206 }
207}
208
209impl Default for QueryRegistry {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215#[derive(Debug, Clone)]
222pub struct CancelToken(Arc<parking_lot::Mutex<Option<CancelReason>>>);
223
224impl CancelToken {
225 pub fn new() -> Self {
226 Self(Arc::new(parking_lot::Mutex::new(None)))
227 }
228
229 pub fn cancel(&self, reason: CancelReason) -> Option<CancelReason> {
232 let mut slot = self.0.lock();
233 if slot.is_some() {
234 None
235 } else {
236 *slot = Some(reason);
237 slot.clone()
238 }
239 }
240
241 pub fn is_cancelled(&self) -> bool {
243 self.0.lock().is_some()
244 }
245
246 pub fn reason(&self) -> Option<CancelReason> {
248 self.0.lock().clone()
249 }
250
251 pub fn clear(&self) {
253 *self.0.lock() = None;
254 }
255}
256
257impl Default for CancelToken {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263#[derive(Debug)]
267pub struct QueryGuard {
268 registry: Option<Arc<QueryRegistry>>,
269 query_id: QueryId,
270 finished: bool,
271}
272
273impl QueryGuard {
274 pub fn new(registry: Arc<QueryRegistry>, query_id: QueryId) -> Self {
275 Self {
276 registry: Some(registry),
277 query_id,
278 finished: false,
279 }
280 }
281
282 pub fn finish(&mut self) {
284 if !self.finished {
285 self.finished = true;
286 if let Some(ref reg) = self.registry {
287 reg.unregister(self.query_id);
288 }
289 }
290 }
291}
292
293impl Drop for QueryGuard {
294 fn drop(&mut self) {
295 if !self.finished {
296 if let Some(ref reg) = self.registry {
297 reg.unregister(self.query_id);
298 }
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 #[test]
307 fn test_register_and_unregister() {
308 let reg = Arc::new(QueryRegistry::new());
309 let meta = QueryMetadata {
310 query_id: QueryId(0),
311 session_id: Some(1),
312 user_name: Some("test".to_string()),
313 space_name: None,
314 query_text: Some("MATCH (n) RETURN n".to_string()),
315 start_time: Instant::now(),
316 };
317 let (id, _) = reg.register(meta);
318 assert!(!id.is_zero());
319 assert_eq!(reg.active_count(), 1);
320
321 assert!(reg.unregister(id));
322 assert_eq!(reg.active_count(), 0);
323 }
324
325 #[test]
326 fn test_cancel_token() {
327 let token = CancelToken::new();
328 assert!(!token.is_cancelled());
329
330 let first = token.cancel(CancelReason::UserKill);
331 assert_eq!(first, Some(CancelReason::UserKill));
332 assert!(token.is_cancelled());
333
334 let second = token.cancel(CancelReason::Deadline);
335 assert!(second.is_none());
336 assert_eq!(token.reason(), Some(CancelReason::UserKill));
337 }
338
339 #[test]
340 fn test_cancel_all() {
341 let reg = Arc::new(QueryRegistry::new());
342 let meta1 = QueryMetadata {
343 query_id: QueryId(0),
344 session_id: None,
345 user_name: None,
346 space_name: None,
347 query_text: None,
348 start_time: Instant::now(),
349 };
350 let meta2 = meta1.clone();
351 reg.register(meta1);
352 reg.register(meta2);
353
354 let cancelled = reg.cancel_all(CancelReason::Shutdown);
355 assert_eq!(cancelled.len(), 2);
356 assert_eq!(reg.active_count(), 2);
357
358 for id in &cancelled {
359 assert_eq!(reg.cancellation_reason(*id), Some(CancelReason::Shutdown));
360 }
361 }
362
363 #[test]
364 fn test_query_guard_drop() {
365 let reg = Arc::new(QueryRegistry::new());
366 let meta = QueryMetadata {
367 query_id: QueryId(0),
368 session_id: None,
369 user_name: None,
370 space_name: None,
371 query_text: None,
372 start_time: Instant::now(),
373 };
374 let (id, _) = reg.register(meta);
375 {
376 let _guard = QueryGuard::new(reg.clone(), id);
377 assert_eq!(reg.active_count(), 1);
378 }
379 assert_eq!(reg.active_count(), 0);
380 }
381
382 #[test]
383 fn test_query_guard_finish() {
384 let reg = Arc::new(QueryRegistry::new());
385 let meta = QueryMetadata {
386 query_id: QueryId(0),
387 session_id: None,
388 user_name: None,
389 space_name: None,
390 query_text: None,
391 start_time: Instant::now(),
392 };
393 let (id, _) = reg.register(meta);
394 let mut guard = QueryGuard::new(reg.clone(), id);
395 guard.finish();
396 assert_eq!(reg.active_count(), 0);
397 }
399
400 #[test]
401 fn test_ids_are_unique_and_non_zero() {
402 let reg = QueryRegistry::new();
403 let mut ids = std::collections::HashSet::new();
404 for _ in 0..100 {
405 let meta = QueryMetadata {
406 query_id: QueryId(0),
407 session_id: None,
408 user_name: None,
409 space_name: None,
410 query_text: None,
411 start_time: Instant::now(),
412 };
413 let (id, _) = reg.register(meta);
414 assert!(!id.is_zero());
415 assert!(ids.insert(id));
416 }
417 }
418
419 #[test]
420 fn test_register_with_requested_id_honored() {
421 let reg = Arc::new(QueryRegistry::new());
422 let meta = QueryMetadata {
423 query_id: QueryId(0),
424 session_id: Some(7),
425 user_name: None,
426 space_name: None,
427 query_text: None,
428 start_time: Instant::now(),
429 };
430 let (id, _) = reg.register_with_id(QueryId(42), meta);
431 assert_eq!(id, QueryId(42), "request-scoped id must be honored");
432 assert_eq!(reg.active_count(), 1);
433
434 let (id2, _) = reg.register_with_id(
435 QueryId(42),
436 QueryMetadata {
437 query_id: QueryId(0),
438 session_id: None,
439 user_name: None,
440 space_name: None,
441 query_text: None,
442 start_time: Instant::now(),
443 },
444 );
445 assert_ne!(
446 id2,
447 QueryId(42),
448 "a duplicate requested id must fall back to a fresh allocation"
449 );
450 assert!(!id2.is_zero());
451 assert_eq!(reg.active_count(), 2);
452 }
453
454 #[test]
455 fn test_register_with_zero_id_allocates() {
456 let reg = Arc::new(QueryRegistry::new());
457 let meta = QueryMetadata {
458 query_id: QueryId(0),
459 session_id: None,
460 user_name: None,
461 space_name: None,
462 query_text: None,
463 start_time: Instant::now(),
464 };
465 let (id, _) = reg.register_with_id(QueryId(0), meta);
466 assert!(!id.is_zero(), "zero is never a valid id");
467 }
468
469 #[test]
470 fn test_register_with_external_token_shares_state() {
471 let reg = Arc::new(QueryRegistry::new());
472 let external = CancelToken::new();
473 let meta = QueryMetadata {
474 query_id: QueryId(0),
475 session_id: None,
476 user_name: None,
477 space_name: None,
478 query_text: None,
479 start_time: Instant::now(),
480 };
481 let (id, token) = reg.register_with_token(QueryId(0), meta.clone(), Some(external.clone()));
482 assert!(!id.is_zero());
483 assert!(!token.is_cancelled());
484 assert_eq!(reg.cancellation_reason(id), None);
485
486 external.cancel(CancelReason::UserKill);
488 assert!(token.is_cancelled());
489 assert_eq!(reg.cancellation_reason(id), Some(CancelReason::UserKill));
490
491 let external2 = CancelToken::new();
493 let (id2, token2) = reg.register_with_token(QueryId(0), meta, Some(external2.clone()));
494 reg.cancel(id2, CancelReason::Deadline);
495 assert!(token2.is_cancelled());
496 assert_eq!(external2.reason(), Some(CancelReason::Deadline));
497 }
498
499 #[test]
500 fn test_register_without_token_allocates_fresh() {
501 let reg = Arc::new(QueryRegistry::new());
502 let meta = QueryMetadata {
503 query_id: QueryId(0),
504 session_id: None,
505 user_name: None,
506 space_name: None,
507 query_text: None,
508 start_time: Instant::now(),
509 };
510 let (id, token) = reg.register_with_token(QueryId(0), meta, None);
511 assert!(!token.is_cancelled());
512 reg.cancel(id, CancelReason::Shutdown);
513 assert!(token.is_cancelled());
514 }
515}