1use crate::{MongrelQueryError, Result, SqlTestHook};
2use mongreldb_core::{CancellationReason, ExecutionControl};
3use parking_lot::Mutex;
4use std::collections::{HashMap, VecDeque};
5use std::fmt;
6use std::str::FromStr;
7use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
8use std::sync::{Arc, Weak};
9use std::time::{Duration, Instant};
10
11const DEFAULT_MAX_ACTIVE: usize = 1_024;
12const DEFAULT_MAX_FINISHED: usize = 2_048;
13const DEFAULT_MAX_FINISHED_BYTES: usize = 2 * 1024 * 1024;
14const DEFAULT_FINISHED_TTL: Duration = Duration::from_secs(60);
15const MAX_METADATA_BYTES: usize = 256;
16const APPROXIMATE_RESERVED_ID_BYTES: usize = 128 + 2 * MAX_METADATA_BYTES;
17
18#[derive(Clone, Copy, PartialEq, Eq, Hash)]
19pub struct QueryId([u8; 16]);
20
21impl QueryId {
22 pub fn random() -> Result<Self> {
23 let mut bytes = [0; 16];
24 getrandom::getrandom(&mut bytes).map_err(|error| {
25 MongrelQueryError::Core(mongreldb_core::MongrelError::Other(format!(
26 "query id randomness failed: {error}"
27 )))
28 })?;
29 Ok(Self(bytes))
30 }
31
32 pub fn as_bytes(&self) -> &[u8; 16] {
33 &self.0
34 }
35}
36
37impl fmt::Display for QueryId {
38 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39 for byte in self.0 {
40 write!(formatter, "{byte:02x}")?;
41 }
42 Ok(())
43 }
44}
45
46impl fmt::Debug for QueryId {
47 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48 fmt::Display::fmt(self, formatter)
49 }
50}
51
52impl FromStr for QueryId {
53 type Err = MongrelQueryError;
54
55 fn from_str(value: &str) -> Result<Self> {
56 if value.len() != 32 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
57 return Err(MongrelQueryError::Core(
58 mongreldb_core::MongrelError::InvalidArgument(
59 "query id must be exactly 32 hexadecimal characters".into(),
60 ),
61 ));
62 }
63 let mut bytes = [0; 16];
64 for (index, chunk) in value.as_bytes().chunks_exact(2).enumerate() {
65 let text = std::str::from_utf8(chunk).map_err(|_| {
66 MongrelQueryError::Core(mongreldb_core::MongrelError::InvalidArgument(
67 "query id is not valid UTF-8".into(),
68 ))
69 })?;
70 bytes[index] = u8::from_str_radix(text, 16).map_err(|_| {
71 MongrelQueryError::Core(mongreldb_core::MongrelError::InvalidArgument(
72 "query id contains invalid hexadecimal".into(),
73 ))
74 })?;
75 }
76 Ok(Self(bytes))
77 }
78}
79
80impl serde::Serialize for QueryId {
81 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
82 where
83 S: serde::Serializer,
84 {
85 serializer.serialize_str(&self.to_string())
86 }
87}
88
89impl<'de> serde::Deserialize<'de> for QueryId {
90 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
91 where
92 D: serde::Deserializer<'de>,
93 {
94 let value = <String as serde::Deserialize>::deserialize(deserializer)?;
95 value.parse().map_err(serde::de::Error::custom)
96 }
97}
98
99#[derive(Debug, Clone, Default)]
100pub struct SqlQueryOptions {
101 pub query_id: Option<QueryId>,
102 pub timeout: Option<Duration>,
103 pub owner: Option<String>,
104 pub session_id: Option<String>,
105 pub parent_control: Option<ExecutionControl>,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109#[repr(u8)]
110pub enum SqlQueryPhase {
111 Queued = 0,
112 Planning = 1,
113 Executing = 2,
114 Streaming = 3,
115 Serializing = 4,
116 CommitCritical = 5,
117 Cancelling = 6,
118 Completed = 7,
119 Failed = 8,
120 Cancelled = 9,
121}
122
123impl SqlQueryPhase {
124 fn from_u8(value: u8) -> Self {
125 match value {
126 0 => Self::Queued,
127 1 => Self::Planning,
128 2 => Self::Executing,
129 3 => Self::Streaming,
130 4 => Self::Serializing,
131 5 => Self::CommitCritical,
132 6 => Self::Cancelling,
133 7 => Self::Completed,
134 8 => Self::Failed,
135 9 => Self::Cancelled,
136 _ => Self::Failed,
137 }
138 }
139
140 fn is_terminal(self) -> bool {
141 matches!(self, Self::Completed | Self::Failed | Self::Cancelled)
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum CancelOutcome {
147 Accepted,
148 AlreadyCancelling,
149 TooLate,
150 AlreadyFinished,
151 NotFound,
152}
153
154#[derive(Debug, Clone, Default, PartialEq, Eq)]
155pub struct DurableOutcome {
156 pub committed: bool,
157 pub committed_statements: usize,
158 pub last_commit_epoch: Option<u64>,
159 pub first_commit_statement_index: Option<usize>,
160 pub last_commit_statement_index: Option<usize>,
161}
162
163impl DurableOutcome {
164 fn record_commit(&mut self, statement_index: usize, epoch: Option<u64>) {
165 self.committed = true;
166 if self.last_commit_statement_index != Some(statement_index) {
167 self.committed_statements = self.committed_statements.saturating_add(1);
168 }
169 self.first_commit_statement_index
170 .get_or_insert(statement_index);
171 self.last_commit_statement_index = Some(statement_index);
172 if epoch.is_some() {
173 self.last_commit_epoch = epoch;
174 }
175 }
176}
177
178#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
179pub enum SerializationOutcome {
180 #[default]
181 NotStarted,
182 InProgress,
183 Succeeded,
184 Failed,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum QueryTerminalErrorCategory {
189 Cancellation,
190 Deadline,
191 ResultLimit,
192 Serialization,
193 Execution,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct QueryTerminalError {
198 pub code: String,
199 pub category: QueryTerminalErrorCategory,
200}
201
202#[derive(Debug, Clone, Default, PartialEq, Eq)]
203struct QueryOutcomeState {
204 durable: DurableOutcome,
205 serialization: SerializationOutcome,
206 terminal_error: Option<QueryTerminalError>,
207 terminal_state_override: Option<QueryTerminalState>,
208 cancellation_reason_override: Option<CancellationReason>,
209 phase_override: Option<SqlQueryPhase>,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum QueryTerminalState {
214 OutcomeUnknown,
215 Completed,
216 FailedBeforeCommit,
217 CancelledBeforeCommit,
218 DeadlineBeforeCommit,
219 Committed,
220 CommittedWithError,
221 PartiallyCommitted,
222 CancelledAfterCommit,
223 DeadlineAfterCommit,
224}
225
226#[derive(Debug, Clone)]
227pub struct QueryStatus {
228 pub query_id: QueryId,
229 pub owner: Option<String>,
230 pub session_id: Option<String>,
231 pub phase: SqlQueryPhase,
232 pub started_at: Instant,
233 pub deadline: Option<Instant>,
234 pub operation: String,
235 pub sql_fingerprint: [u8; 32],
236 pub cancellation_reason: CancellationReason,
237 pub committed: bool,
239 pub durable_outcome: DurableOutcome,
240 pub terminal_error: Option<QueryTerminalError>,
241 pub serialization_outcome: SerializationOutcome,
242 pub outcome_unknown: bool,
243 terminal_state_override: Option<QueryTerminalState>,
244 pub completed_statements: usize,
245 pub statement_index: usize,
246 pub cancel_requested_at: Option<Instant>,
247 pub queue_duration: Duration,
248 pub planning_duration: Duration,
249 pub execution_duration: Duration,
250 pub serialization_duration: Duration,
251 pub cancel_requested_phase: Option<SqlQueryPhase>,
252 pub cancel_observed_phase: Option<SqlQueryPhase>,
253 pub commit_fence_outcome: CommitFenceOutcome,
254}
255
256impl QueryStatus {
257 pub fn terminal_state(&self) -> Option<QueryTerminalState> {
258 if !self.phase.is_terminal() {
259 return None;
260 }
261 if let Some(terminal_state) = self.terminal_state_override {
262 return Some(terminal_state);
263 }
264 if !self.durable_outcome.committed {
265 return Some(match (self.phase, self.cancellation_reason) {
266 (SqlQueryPhase::Completed, _) => QueryTerminalState::Completed,
267 (SqlQueryPhase::Cancelled, CancellationReason::Deadline) => {
268 QueryTerminalState::DeadlineBeforeCommit
269 }
270 (SqlQueryPhase::Cancelled, _) => QueryTerminalState::CancelledBeforeCommit,
271 _ => QueryTerminalState::FailedBeforeCommit,
272 });
273 }
274 Some(match (self.phase, self.cancellation_reason) {
275 (SqlQueryPhase::Completed, _) => QueryTerminalState::Committed,
276 (SqlQueryPhase::Cancelled, CancellationReason::Deadline) => {
277 QueryTerminalState::DeadlineAfterCommit
278 }
279 (SqlQueryPhase::Cancelled, _) => QueryTerminalState::CancelledAfterCommit,
280 _ if self
281 .durable_outcome
282 .last_commit_statement_index
283 .is_some_and(|index| index < self.statement_index) =>
284 {
285 QueryTerminalState::PartiallyCommitted
286 }
287 _ => QueryTerminalState::CommittedWithError,
288 })
289 }
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
293pub enum CommitFenceOutcome {
294 NotReached,
295 CancelWon,
296 CommitWon,
297}
298
299#[derive(Debug, Clone)]
300struct QueryTrace {
301 phase_started_at: Instant,
302 queue_duration: Duration,
303 planning_duration: Duration,
304 execution_duration: Duration,
305 serialization_duration: Duration,
306 cancel_requested_phase: Option<SqlQueryPhase>,
307 cancel_observed_phase: Option<SqlQueryPhase>,
308 commit_fence_outcome: CommitFenceOutcome,
309}
310
311impl QueryTrace {
312 fn new(started_at: Instant) -> Self {
313 Self {
314 phase_started_at: started_at,
315 queue_duration: Duration::ZERO,
316 planning_duration: Duration::ZERO,
317 execution_duration: Duration::ZERO,
318 serialization_duration: Duration::ZERO,
319 cancel_requested_phase: None,
320 cancel_observed_phase: None,
321 commit_fence_outcome: CommitFenceOutcome::NotReached,
322 }
323 }
324
325 fn transition(&mut self, phase: SqlQueryPhase) {
326 let now = Instant::now();
327 let elapsed = now.saturating_duration_since(self.phase_started_at);
328 match phase {
329 SqlQueryPhase::Queued => self.queue_duration += elapsed,
330 SqlQueryPhase::Planning => self.planning_duration += elapsed,
331 SqlQueryPhase::Executing | SqlQueryPhase::Streaming | SqlQueryPhase::CommitCritical => {
332 self.execution_duration += elapsed
333 }
334 SqlQueryPhase::Serializing => self.serialization_duration += elapsed,
335 SqlQueryPhase::Cancelling
336 | SqlQueryPhase::Completed
337 | SqlQueryPhase::Failed
338 | SqlQueryPhase::Cancelled => {}
339 }
340 self.phase_started_at = now;
341 }
342}
343
344#[derive(Debug)]
345struct RegisteredQuery {
346 id: QueryId,
347 owner: Option<String>,
348 session_id: Option<String>,
349 control: ExecutionControl,
350 phase: AtomicU8,
351 started_at: Instant,
352 deadline: Option<Instant>,
353 operation: Mutex<String>,
354 sql_fingerprint: Mutex<[u8; 32]>,
355 outcome: Mutex<QueryOutcomeState>,
356 completed_statements: AtomicUsize,
357 statement_index: AtomicUsize,
358 cancel_requested_at: Mutex<Option<Instant>>,
359 trace: Mutex<QueryTrace>,
360}
361
362impl RegisteredQuery {
363 fn phase(&self) -> SqlQueryPhase {
364 SqlQueryPhase::from_u8(self.phase.load(Ordering::Acquire))
365 }
366
367 fn status(&self) -> QueryStatus {
368 let outcome = self.outcome.lock().clone();
369 let phase = outcome.phase_override.unwrap_or_else(|| self.phase());
370 let mut trace = self.trace.lock().clone();
371 trace.transition(phase);
372 QueryStatus {
373 query_id: self.id,
374 owner: self.owner.clone(),
375 session_id: self.session_id.clone(),
376 phase,
377 started_at: self.started_at,
378 deadline: self.deadline,
379 operation: self.operation.lock().clone(),
380 sql_fingerprint: *self.sql_fingerprint.lock(),
381 cancellation_reason: outcome
382 .cancellation_reason_override
383 .unwrap_or_else(|| self.control.reason()),
384 committed: outcome.durable.committed,
385 durable_outcome: outcome.durable,
386 terminal_error: outcome.terminal_error,
387 serialization_outcome: outcome.serialization,
388 outcome_unknown: outcome.terminal_state_override
389 == Some(QueryTerminalState::OutcomeUnknown),
390 terminal_state_override: outcome.terminal_state_override,
391 completed_statements: self.completed_statements.load(Ordering::Acquire),
392 statement_index: self.statement_index.load(Ordering::Acquire),
393 cancel_requested_at: *self.cancel_requested_at.lock(),
394 queue_duration: trace.queue_duration,
395 planning_duration: trace.planning_duration,
396 execution_duration: trace.execution_duration,
397 serialization_duration: trace.serialization_duration,
398 cancel_requested_phase: trace.cancel_requested_phase,
399 cancel_observed_phase: trace.cancel_observed_phase,
400 commit_fence_outcome: trace.commit_fence_outcome,
401 }
402 }
403
404 fn record_transition(&self, phase: SqlQueryPhase) {
405 self.trace.lock().transition(phase);
406 }
407}
408
409#[derive(Debug, Clone)]
410struct FinishedQuery {
411 status: QueryStatus,
412 finished_at: Instant,
413 approximate_bytes: usize,
414}
415
416#[derive(Debug, Clone, Copy)]
417struct ReservedQueryId {
418 query_id: QueryId,
419 finished_at: Instant,
420}
421
422#[derive(Debug, Default)]
423struct RegistryState {
424 active: HashMap<QueryId, Arc<RegisteredQuery>>,
425 finished: VecDeque<FinishedQuery>,
426 finished_bytes: usize,
427 reserved_ids: HashMap<QueryId, (Option<String>, Option<String>)>,
428 reservations: VecDeque<ReservedQueryId>,
429}
430
431#[derive(Debug)]
432pub struct SqlQueryRegistry {
433 state: Mutex<RegistryState>,
434 max_active: usize,
435 max_finished: usize,
436 max_finished_bytes: usize,
437 max_tracked_ids: usize,
438 finished_ttl: Duration,
439}
440
441impl Default for SqlQueryRegistry {
442 fn default() -> Self {
443 Self::new(
444 DEFAULT_MAX_ACTIVE,
445 DEFAULT_MAX_FINISHED,
446 DEFAULT_MAX_FINISHED_BYTES,
447 DEFAULT_FINISHED_TTL,
448 )
449 }
450}
451
452impl SqlQueryRegistry {
453 pub fn new(
454 max_active: usize,
455 max_finished: usize,
456 max_finished_bytes: usize,
457 finished_ttl: Duration,
458 ) -> Self {
459 let compact_reservations = max_finished
460 .max(max_finished_bytes / APPROXIMATE_RESERVED_ID_BYTES)
461 .max(1);
462 Self {
463 state: Mutex::new(RegistryState::default()),
464 max_active: max_active.max(1),
465 max_finished,
466 max_finished_bytes,
467 max_tracked_ids: max_active.max(1).saturating_add(compact_reservations),
468 finished_ttl,
469 }
470 }
471
472 pub fn register(self: &Arc<Self>, options: SqlQueryOptions) -> Result<RegisteredSqlQuery> {
473 validate_metadata("owner", options.owner.as_deref())?;
474 validate_metadata("session id", options.session_id.as_deref())?;
475 let id = match options.query_id {
476 Some(id) => id,
477 None => QueryId::random()?,
478 };
479 let deadline_base = Instant::now();
480 let deadline = options
481 .timeout
482 .map(|timeout| {
483 deadline_base.checked_add(timeout).ok_or_else(|| {
484 MongrelQueryError::Core(mongreldb_core::MongrelError::InvalidArgument(
485 "query timeout exceeds the monotonic clock range".into(),
486 ))
487 })
488 })
489 .transpose()?;
490 let control = match options.parent_control {
491 Some(parent) => parent.child_with_deadline(deadline),
492 None => ExecutionControl::new(deadline),
493 };
494 let started_at = Instant::now();
495 let query = Arc::new(RegisteredQuery {
496 id,
497 owner: options.owner,
498 session_id: options.session_id,
499 deadline: control.deadline(),
500 control,
501 phase: AtomicU8::new(SqlQueryPhase::Queued as u8),
502 started_at,
503 operation: Mutex::new("UNKNOWN".into()),
504 sql_fingerprint: Mutex::new([0; 32]),
505 outcome: Mutex::new(QueryOutcomeState::default()),
506 completed_statements: AtomicUsize::new(0),
507 statement_index: AtomicUsize::new(0),
508 cancel_requested_at: Mutex::new(None),
509 trace: Mutex::new(QueryTrace::new(started_at)),
510 });
511 let mut state = self.state.lock();
512 self.prune_locked(&mut state);
513 if state.active.contains_key(&id) || state.reserved_ids.contains_key(&id) {
514 return Err(MongrelQueryError::QueryIdConflict { query_id: id });
515 }
516 if state.active.len() >= self.max_active
517 || state.active.len().saturating_add(state.reserved_ids.len()) >= self.max_tracked_ids
518 {
519 return Err(MongrelQueryError::QueryRegistryFull);
520 }
521 state.active.insert(id, Arc::clone(&query));
522 Ok(RegisteredSqlQuery {
523 registry: Arc::downgrade(self),
524 query,
525 })
526 }
527
528 pub fn cancel(&self, query_id: QueryId) -> CancelOutcome {
529 let query = {
530 let mut state = self.state.lock();
531 self.prune_locked(&mut state);
532 if let Some(query) = state.active.get(&query_id) {
533 Some(Arc::clone(query))
534 } else if state.reserved_ids.contains_key(&query_id) {
535 return CancelOutcome::AlreadyFinished;
536 } else {
537 return CancelOutcome::NotFound;
538 }
539 };
540 match query {
541 Some(query) => query.request_cancel(CancellationReason::ClientRequest),
542 None => CancelOutcome::NotFound,
543 }
544 }
545
546 pub fn status(&self, query_id: QueryId) -> Option<QueryStatus> {
547 let mut state = self.state.lock();
548 self.prune_locked(&mut state);
549 state
550 .active
551 .get(&query_id)
552 .map(|query| query.status())
553 .or_else(|| {
554 state
555 .finished
556 .iter()
557 .rev()
558 .find(|finished| finished.status.query_id == query_id)
559 .map(|finished| finished.status.clone())
560 })
561 }
562
563 pub fn compact_finished_identity(
565 &self,
566 query_id: QueryId,
567 ) -> Option<(Option<String>, Option<String>)> {
568 let mut state = self.state.lock();
569 self.prune_locked(&mut state);
570 state.reserved_ids.get(&query_id).cloned()
571 }
572
573 pub fn cancel_session(&self, session_id: &str, reason: CancellationReason) -> usize {
574 let queries = {
575 let state = self.state.lock();
576 state
577 .active
578 .values()
579 .filter(|query| query.session_id.as_deref() == Some(session_id))
580 .cloned()
581 .collect::<Vec<_>>()
582 };
583 let mut accepted = 0;
584 for query in queries {
585 if query.request_cancel(reason) == CancelOutcome::Accepted {
586 accepted += 1;
587 }
588 }
589 accepted
590 }
591
592 pub fn cancel_all(&self, reason: CancellationReason) -> usize {
593 let queries = self
594 .state
595 .lock()
596 .active
597 .values()
598 .cloned()
599 .collect::<Vec<_>>();
600 let mut accepted = 0;
601 for query in queries {
602 if query.request_cancel(reason) == CancelOutcome::Accepted {
603 accepted += 1;
604 }
605 }
606 accepted
607 }
608
609 pub fn active_count(&self) -> usize {
610 self.state.lock().active.len()
611 }
612
613 pub fn active_statuses(&self) -> Vec<QueryStatus> {
614 self.state
615 .lock()
616 .active
617 .values()
618 .map(|query| query.status())
619 .collect()
620 }
621
622 pub fn active_for_session(&self, session_id: &str) -> usize {
623 self.state
624 .lock()
625 .active
626 .values()
627 .filter(|query| query.session_id.as_deref() == Some(session_id))
628 .count()
629 }
630
631 pub fn queued_count(&self) -> usize {
632 self.state
633 .lock()
634 .active
635 .values()
636 .filter(|query| query.phase() == SqlQueryPhase::Queued)
637 .count()
638 }
639
640 pub fn entry_count(&self) -> usize {
641 let mut state = self.state.lock();
642 self.prune_locked(&mut state);
643 state.active.len() + state.finished.len()
644 }
645
646 pub fn approximate_bytes(&self) -> usize {
647 let mut state = self.state.lock();
648 self.prune_locked(&mut state);
649 state.finished_bytes
650 + state
651 .active
652 .len()
653 .saturating_mul(std::mem::size_of::<RegisteredQuery>())
654 + state
655 .reserved_ids
656 .len()
657 .saturating_mul(APPROXIMATE_RESERVED_ID_BYTES)
658 }
659
660 pub fn finished_count(&self) -> usize {
661 let mut state = self.state.lock();
662 self.prune_locked(&mut state);
663 state.finished.len()
664 }
665
666 fn finish(&self, query: &Arc<RegisteredQuery>) {
667 debug_assert!(query.phase().is_terminal());
668 let status = query.status();
669 let approximate_bytes = std::mem::size_of::<FinishedQuery>()
670 + status.owner.as_ref().map_or(0, String::len)
671 + status.session_id.as_ref().map_or(0, String::len)
672 + status.operation.len()
673 + status
674 .terminal_error
675 .as_ref()
676 .map_or(0, |error| error.code.len());
677 let mut state = self.state.lock();
678 if state.active.remove(&query.id).is_none() {
679 return;
680 }
681 let finished_at = Instant::now();
682 state
683 .reserved_ids
684 .insert(query.id, (status.owner.clone(), status.session_id.clone()));
685 state.reservations.push_back(ReservedQueryId {
686 query_id: query.id,
687 finished_at,
688 });
689 if self.max_finished > 0 && self.max_finished_bytes > 0 {
690 state.finished_bytes = state.finished_bytes.saturating_add(approximate_bytes);
691 state.finished.push_back(FinishedQuery {
692 status,
693 finished_at,
694 approximate_bytes,
695 });
696 }
697 self.prune_locked(&mut state);
698 }
699
700 fn prune_locked(&self, state: &mut RegistryState) {
701 let now = Instant::now();
702 while state.reservations.front().is_some_and(|entry| {
703 now.saturating_duration_since(entry.finished_at) >= self.finished_ttl
704 }) {
705 if let Some(entry) = state.reservations.pop_front() {
706 state.reserved_ids.remove(&entry.query_id);
707 }
708 }
709 while state.finished.front().is_some_and(|entry| {
710 now.saturating_duration_since(entry.finished_at) >= self.finished_ttl
711 || state.finished.len() > self.max_finished
712 || state.finished_bytes > self.max_finished_bytes
713 }) {
714 if let Some(entry) = state.finished.pop_front() {
715 state.finished_bytes = state.finished_bytes.saturating_sub(entry.approximate_bytes);
716 }
717 }
718 }
719}
720
721impl RegisteredQuery {
722 fn request_cancel(&self, reason: CancellationReason) -> CancelOutcome {
723 loop {
724 let phase = self.phase();
725 match phase {
726 SqlQueryPhase::Queued
727 | SqlQueryPhase::Planning
728 | SqlQueryPhase::Executing
729 | SqlQueryPhase::Streaming
730 | SqlQueryPhase::Serializing => {
731 if self
732 .phase
733 .compare_exchange(
734 phase as u8,
735 SqlQueryPhase::Cancelling as u8,
736 Ordering::AcqRel,
737 Ordering::Acquire,
738 )
739 .is_ok()
740 {
741 self.control.cancel(reason);
742 *self.cancel_requested_at.lock() = Some(Instant::now());
743 let mut trace = self.trace.lock();
744 trace.transition(phase);
745 trace.cancel_requested_phase = Some(phase);
746 if trace.commit_fence_outcome == CommitFenceOutcome::NotReached {
747 trace.commit_fence_outcome = CommitFenceOutcome::CancelWon;
748 }
749 return CancelOutcome::Accepted;
750 }
751 }
752 SqlQueryPhase::Cancelling | SqlQueryPhase::Cancelled => {
753 return CancelOutcome::AlreadyCancelling;
754 }
755 SqlQueryPhase::CommitCritical => return CancelOutcome::TooLate,
756 SqlQueryPhase::Completed | SqlQueryPhase::Failed => {
757 return CancelOutcome::AlreadyFinished;
758 }
759 }
760 }
761 }
762}
763
764#[derive(Debug, Clone)]
765pub struct RegisteredSqlQuery {
766 registry: Weak<SqlQueryRegistry>,
767 query: Arc<RegisteredQuery>,
768}
769
770enum TerminalFailure {
771 Error {
772 code: String,
773 category: QueryTerminalErrorCategory,
774 },
775 Serialization(String),
776}
777
778pub(crate) struct SqlTaskContext {
781 query: RegisteredSqlQuery,
782 test_hook: Option<SqlTestHook>,
783}
784
785impl SqlTaskContext {
786 pub(crate) fn new(query: RegisteredSqlQuery, test_hook: Option<SqlTestHook>) -> Self {
787 Self { query, test_hook }
788 }
789
790 pub(crate) fn query(&self) -> &RegisteredSqlQuery {
791 &self.query
792 }
793
794 pub(crate) fn test_hook(&self) -> Option<&SqlTestHook> {
795 self.test_hook.as_ref()
796 }
797}
798
799impl RegisteredSqlQuery {
800 pub fn id(&self) -> QueryId {
801 self.query.id
802 }
803
804 pub fn control(&self) -> &ExecutionControl {
805 &self.query.control
806 }
807
808 pub fn phase(&self) -> SqlQueryPhase {
809 self.query.phase()
810 }
811
812 pub fn status(&self) -> QueryStatus {
813 self.query.status()
814 }
815
816 pub fn set_sql_metadata(&self, sql: &str) {
817 let operation = sql
818 .split_whitespace()
819 .next()
820 .unwrap_or("UNKNOWN")
821 .chars()
822 .take(64)
823 .collect::<String>()
824 .to_ascii_uppercase();
825 *self.query.operation.lock() = operation;
826 *self.query.sql_fingerprint.lock() = crate::normalized_sql_fingerprint(sql);
827 }
828
829 pub fn transition(&self, expected: SqlQueryPhase, next: SqlQueryPhase) -> Result<()> {
830 self.query
831 .phase
832 .compare_exchange(
833 expected as u8,
834 next as u8,
835 Ordering::AcqRel,
836 Ordering::Acquire,
837 )
838 .map(|_| self.query.record_transition(expected))
839 .map_err(|actual| {
840 let actual = SqlQueryPhase::from_u8(actual);
841 if actual == SqlQueryPhase::Cancelling {
842 self.cancellation_error()
843 } else {
844 MongrelQueryError::InvalidQueryState(format!(
845 "query {} expected {expected:?}, found {actual:?}",
846 self.id()
847 ))
848 }
849 })
850 }
851
852 pub fn enter_commit_critical(&self) -> Result<()> {
853 if let Err(error) = self.checkpoint() {
854 self.query.trace.lock().commit_fence_outcome = CommitFenceOutcome::CancelWon;
855 return Err(error);
856 }
857 self.transition(SqlQueryPhase::Executing, SqlQueryPhase::CommitCritical)?;
858 self.query.trace.lock().commit_fence_outcome = CommitFenceOutcome::CommitWon;
859 Ok(())
860 }
861
862 pub fn exit_commit_critical(&self) -> Result<()> {
863 self.transition(SqlQueryPhase::CommitCritical, SqlQueryPhase::Executing)
864 }
865
866 pub fn begin_serialization(&self) -> Result<()> {
867 self.checkpoint()?;
868 match self.phase() {
869 SqlQueryPhase::Executing => {
870 self.transition(SqlQueryPhase::Executing, SqlQueryPhase::Serializing)
871 }
872 SqlQueryPhase::Streaming => {
873 self.transition(SqlQueryPhase::Streaming, SqlQueryPhase::Serializing)
874 }
875 phase => Err(MongrelQueryError::InvalidQueryState(format!(
876 "query {} cannot serialize from {phase:?}",
877 self.id()
878 ))),
879 }?;
880 self.query.outcome.lock().serialization = SerializationOutcome::InProgress;
881 Ok(())
882 }
883
884 pub fn record_commit(&self, statement_index: usize, epoch: u64) {
886 self.query
887 .outcome
888 .lock()
889 .durable
890 .record_commit(statement_index, Some(epoch));
891 }
892
893 pub fn durable_outcome(&self) -> DurableOutcome {
894 self.query.outcome.lock().durable.clone()
895 }
896
897 pub fn commit_outcome_error(&self, message: impl Into<String>) -> MongrelQueryError {
898 let durable = self.durable_outcome();
899 MongrelQueryError::CommitOutcome {
900 query_id: self.id(),
901 committed: durable.committed,
902 committed_statements: durable.committed_statements,
903 last_commit_epoch: durable.last_commit_epoch,
904 first_commit_statement_index: durable.first_commit_statement_index,
905 last_commit_statement_index: durable.last_commit_statement_index,
906 completed_statements: self.query.completed_statements.load(Ordering::Acquire),
907 statement_index: self.query.statement_index.load(Ordering::Acquire),
908 message: message.into(),
909 }
910 }
911
912 pub fn result_limit_error(&self, message: impl Into<String>) -> MongrelQueryError {
913 let durable = self.durable_outcome();
914 MongrelQueryError::ResultLimitExceeded {
915 query_id: self.id(),
916 committed: durable.committed,
917 committed_statements: durable.committed_statements,
918 last_commit_epoch: durable.last_commit_epoch,
919 first_commit_statement_index: durable.first_commit_statement_index,
920 last_commit_statement_index: durable.last_commit_statement_index,
921 completed_statements: self.query.completed_statements.load(Ordering::Acquire),
922 statement_index: self.query.statement_index.load(Ordering::Acquire),
923 message: message.into(),
924 }
925 }
926
927 pub fn outcome_unknown_error(&self, message: impl Into<String>) -> MongrelQueryError {
930 let message = message.into();
931 self.mark_outcome_unknown();
932 MongrelQueryError::OutcomeUnknown {
933 query_id: self.id(),
934 message,
935 }
936 }
937
938 #[allow(clippy::too_many_arguments)]
941 pub fn restore_replayed_outcome(
942 &self,
943 durable: DurableOutcome,
944 completed_statements: usize,
945 statement_index: usize,
946 serialization: SerializationOutcome,
947 terminal_error: Option<QueryTerminalError>,
948 terminal_state: QueryTerminalState,
949 cancellation_reason: CancellationReason,
950 phase: SqlQueryPhase,
951 ) {
952 let mut outcome = self.query.outcome.lock();
953 outcome.durable = durable;
954 outcome.serialization = serialization;
955 outcome.terminal_error = terminal_error;
956 outcome.terminal_state_override = Some(terminal_state);
957 outcome.cancellation_reason_override = Some(cancellation_reason);
958 outcome.phase_override = Some(phase);
959 self.query
960 .completed_statements
961 .store(completed_statements, Ordering::Release);
962 self.query
963 .statement_index
964 .store(statement_index, Ordering::Release);
965 }
966
967 pub fn mark_outcome_unknown(&self) {
970 let mut outcome = self.query.outcome.lock();
971 outcome.terminal_state_override = Some(QueryTerminalState::OutcomeUnknown);
972 outcome.terminal_error = Some(QueryTerminalError {
973 code: "QUERY_OUTCOME_UNKNOWN".into(),
974 category: QueryTerminalErrorCategory::Execution,
975 });
976 }
977
978 pub fn record_terminal_error(
979 &self,
980 code: impl Into<String>,
981 category: QueryTerminalErrorCategory,
982 ) {
983 Self::set_terminal_error(&mut self.query.outcome.lock(), code.into(), category);
984 }
985
986 pub fn record_serialization_failure(&self, code: impl Into<String>) {
987 let mut outcome = self.query.outcome.lock();
988 Self::set_serialization_failure(&mut outcome, code.into());
989 }
990
991 fn set_terminal_error(
992 outcome: &mut QueryOutcomeState,
993 code: String,
994 category: QueryTerminalErrorCategory,
995 ) {
996 outcome.terminal_error = Some(QueryTerminalError { code, category });
997 }
998
999 fn set_serialization_failure(outcome: &mut QueryOutcomeState, code: String) {
1000 let code = if outcome.durable.committed && code.starts_with("SERIALIZATION_") {
1001 "SERIALIZATION_FAILED_AFTER_COMMIT".into()
1002 } else {
1003 code
1004 };
1005 outcome.serialization = SerializationOutcome::Failed;
1006 outcome.terminal_error = Some(QueryTerminalError {
1007 code,
1008 category: QueryTerminalErrorCategory::Serialization,
1009 });
1010 }
1011
1012 pub fn begin_statement(&self, index: usize) {
1013 self.query.statement_index.store(index, Ordering::Release);
1014 }
1015
1016 pub fn complete_statement(&self, index: usize) {
1017 self.query
1018 .completed_statements
1019 .store(index.saturating_add(1), Ordering::Release);
1020 }
1021
1022 pub fn complete_current_statement(&self) {
1023 let index = self.query.statement_index.load(Ordering::Acquire);
1024 self.complete_statement(index);
1025 }
1026
1027 pub fn request_cancel(&self, reason: CancellationReason) -> CancelOutcome {
1028 self.query.request_cancel(reason)
1029 }
1030
1031 pub fn checkpoint(&self) -> Result<()> {
1032 let durable = self.durable_outcome();
1033 let result = self
1034 .query
1035 .control
1036 .checkpoint()
1037 .map_err(|error| match error {
1038 mongreldb_core::MongrelError::DeadlineExceeded => {
1039 MongrelQueryError::DeadlineExceeded {
1040 query_id: self.id(),
1041 timeout_ms: self.query.deadline.map(|deadline| {
1042 deadline
1043 .saturating_duration_since(self.query.started_at)
1044 .as_millis()
1045 .min(u128::from(u64::MAX)) as u64
1046 }),
1047 committed: durable.committed,
1048 committed_statements: durable.committed_statements,
1049 last_commit_epoch: durable.last_commit_epoch,
1050 first_commit_statement_index: durable.first_commit_statement_index,
1051 last_commit_statement_index: durable.last_commit_statement_index,
1052 completed_statements: self
1053 .query
1054 .completed_statements
1055 .load(Ordering::Acquire),
1056 cancelled_statement_index: self
1057 .query
1058 .statement_index
1059 .load(Ordering::Acquire),
1060 }
1061 }
1062 mongreldb_core::MongrelError::Cancelled => self.cancellation_error(),
1063 other => MongrelQueryError::Core(other),
1064 });
1065 if result.is_err() && self.query.control.is_cancelled() {
1066 let mut trace = self.query.trace.lock();
1067 trace.cancel_observed_phase = trace.cancel_requested_phase.or(Some(self.phase()));
1068 }
1069 result
1070 }
1071
1072 pub fn complete(&self) -> Result<()> {
1073 self.try_complete()
1074 }
1075
1076 pub fn try_complete(&self) -> Result<()> {
1077 if self.phase() == SqlQueryPhase::Completed {
1078 self.finish(SqlQueryPhase::Completed, None);
1079 return Ok(());
1080 }
1081 if let Err(error) = self.checkpoint() {
1082 self.fail();
1083 return Err(error);
1084 }
1085 let phase = self.finish(SqlQueryPhase::Completed, None);
1086 match phase {
1087 SqlQueryPhase::Completed => Ok(()),
1088 SqlQueryPhase::Cancelled => Err(self.cancellation_error()),
1089 other => Err(MongrelQueryError::InvalidQueryState(format!(
1090 "query {} completed from terminal phase {other:?}",
1091 self.id()
1092 ))),
1093 }
1094 }
1095
1096 pub fn fail(&self) {
1097 self.finish(SqlQueryPhase::Failed, None);
1098 }
1099
1100 pub fn fail_result_limit(&self) {
1101 self.fail_with_error(
1102 "RESULT_LIMIT_EXCEEDED",
1103 QueryTerminalErrorCategory::ResultLimit,
1104 );
1105 }
1106
1107 pub fn fail_with_error(&self, code: impl Into<String>, category: QueryTerminalErrorCategory) {
1108 self.finish(
1109 SqlQueryPhase::Failed,
1110 Some(TerminalFailure::Error {
1111 code: code.into(),
1112 category,
1113 }),
1114 );
1115 }
1116
1117 pub fn fail_serialization(&self) {
1118 self.finish(
1119 SqlQueryPhase::Failed,
1120 Some(TerminalFailure::Serialization(
1121 "SERIALIZATION_FAILED".into(),
1122 )),
1123 );
1124 }
1125
1126 fn finish(
1127 &self,
1128 requested: SqlQueryPhase,
1129 mut failure: Option<TerminalFailure>,
1130 ) -> SqlQueryPhase {
1131 debug_assert!(matches!(
1132 requested,
1133 SqlQueryPhase::Completed | SqlQueryPhase::Failed
1134 ));
1135 let explicit_failure = failure.is_some();
1136 let mut outcome = self.query.outcome.lock();
1137 let phase = loop {
1138 let current = self.phase();
1139 if current.is_terminal() {
1140 break current;
1141 }
1142 let terminal = if current == SqlQueryPhase::Cancelling
1143 || (!explicit_failure && self.query.control.is_cancelled())
1144 {
1145 while !self.query.control.is_cancelled() {
1146 std::hint::spin_loop();
1147 }
1148 SqlQueryPhase::Cancelled
1149 } else {
1150 requested
1151 };
1152 if self
1153 .query
1154 .phase
1155 .compare_exchange(
1156 current as u8,
1157 terminal as u8,
1158 Ordering::AcqRel,
1159 Ordering::Acquire,
1160 )
1161 .is_ok()
1162 {
1163 self.query.record_transition(current);
1164 if terminal == SqlQueryPhase::Cancelled && outcome.phase_override.is_some() {
1165 outcome.phase_override = None;
1166 outcome.terminal_state_override = None;
1167 outcome.cancellation_reason_override = None;
1168 outcome.terminal_error = None;
1169 outcome.serialization = SerializationOutcome::Failed;
1170 }
1171 if terminal == SqlQueryPhase::Failed {
1172 match failure.take() {
1173 Some(TerminalFailure::Error { code, category }) => {
1174 Self::set_terminal_error(&mut outcome, code, category);
1175 }
1176 Some(TerminalFailure::Serialization(code)) => {
1177 Self::set_serialization_failure(&mut outcome, code);
1178 }
1179 None => {}
1180 }
1181 }
1182 Self::finalize_outcome(&mut outcome, terminal, self.query.control.reason());
1183 break terminal;
1184 }
1185 };
1186 Self::finalize_outcome(&mut outcome, phase, self.query.control.reason());
1187 drop(outcome);
1188 if let Some(registry) = self.registry.upgrade() {
1189 registry.finish(&self.query);
1190 }
1191 phase
1192 }
1193
1194 fn finalize_outcome(
1195 outcome: &mut QueryOutcomeState,
1196 phase: SqlQueryPhase,
1197 reason: CancellationReason,
1198 ) {
1199 match (phase, outcome.serialization) {
1200 (SqlQueryPhase::Completed, SerializationOutcome::InProgress) => {
1201 outcome.serialization = SerializationOutcome::Succeeded;
1202 }
1203 (
1204 SqlQueryPhase::Failed | SqlQueryPhase::Cancelled,
1205 SerializationOutcome::InProgress,
1206 ) => {
1207 outcome.serialization = SerializationOutcome::Failed;
1208 }
1209 _ => {}
1210 }
1211 if phase == SqlQueryPhase::Completed || outcome.terminal_error.is_some() {
1212 return;
1213 }
1214 let committed = outcome.durable.committed;
1215 let (code, category) = match reason {
1216 CancellationReason::Deadline => (
1217 if committed {
1218 "DEADLINE_AFTER_COMMIT"
1219 } else {
1220 "DEADLINE_EXCEEDED"
1221 },
1222 QueryTerminalErrorCategory::Deadline,
1223 ),
1224 CancellationReason::None if outcome.serialization == SerializationOutcome::Failed => (
1225 if committed {
1226 "SERIALIZATION_FAILED_AFTER_COMMIT"
1227 } else {
1228 "SERIALIZATION_FAILED"
1229 },
1230 QueryTerminalErrorCategory::Serialization,
1231 ),
1232 CancellationReason::None => ("QUERY_FAILED", QueryTerminalErrorCategory::Execution),
1233 _ => (
1234 if committed {
1235 "QUERY_CANCELLED_AFTER_COMMIT"
1236 } else {
1237 "QUERY_CANCELLED"
1238 },
1239 QueryTerminalErrorCategory::Cancellation,
1240 ),
1241 };
1242 outcome.terminal_error = Some(QueryTerminalError {
1243 code: code.into(),
1244 category,
1245 });
1246 }
1247
1248 fn cancellation_error(&self) -> MongrelQueryError {
1249 let durable = self.durable_outcome();
1250 let mut reason = self.query.control.reason();
1251 while reason == CancellationReason::None && self.query.phase() == SqlQueryPhase::Cancelling
1252 {
1253 std::thread::yield_now();
1254 reason = self.query.control.reason();
1255 }
1256 match reason {
1257 CancellationReason::Deadline => MongrelQueryError::DeadlineExceeded {
1258 query_id: self.id(),
1259 timeout_ms: self.query.deadline.map(|deadline| {
1260 deadline
1261 .saturating_duration_since(self.query.started_at)
1262 .as_millis()
1263 .min(u128::from(u64::MAX)) as u64
1264 }),
1265 committed: durable.committed,
1266 committed_statements: durable.committed_statements,
1267 last_commit_epoch: durable.last_commit_epoch,
1268 first_commit_statement_index: durable.first_commit_statement_index,
1269 last_commit_statement_index: durable.last_commit_statement_index,
1270 completed_statements: self.query.completed_statements.load(Ordering::Acquire),
1271 cancelled_statement_index: self.query.statement_index.load(Ordering::Acquire),
1272 },
1273 reason => MongrelQueryError::QueryCancelled {
1274 query_id: self.id(),
1275 reason,
1276 committed: durable.committed,
1277 committed_statements: durable.committed_statements,
1278 last_commit_epoch: durable.last_commit_epoch,
1279 first_commit_statement_index: durable.first_commit_statement_index,
1280 last_commit_statement_index: durable.last_commit_statement_index,
1281 completed_statements: self.query.completed_statements.load(Ordering::Acquire),
1282 cancelled_statement_index: self.query.statement_index.load(Ordering::Acquire),
1283 },
1284 }
1285 }
1286}
1287
1288pub struct RegisteredQueryGuard {
1289 query: Option<RegisteredSqlQuery>,
1290}
1291
1292impl RegisteredQueryGuard {
1293 pub fn new(query: RegisteredSqlQuery) -> Self {
1294 Self { query: Some(query) }
1295 }
1296
1297 pub fn query(&self) -> &RegisteredSqlQuery {
1298 self.query
1299 .as_ref()
1300 .expect("registered query guard consumed")
1301 }
1302
1303 pub fn complete(self) -> Result<()> {
1304 self.try_complete()
1305 }
1306
1307 pub fn try_complete(mut self) -> Result<()> {
1308 if let Some(query) = self.query.take() {
1309 query.try_complete()
1310 } else {
1311 Ok(())
1312 }
1313 }
1314
1315 pub fn fail(mut self) {
1316 if let Some(query) = self.query.take() {
1317 query.fail();
1318 }
1319 }
1320
1321 pub fn fail_result_limit(mut self) {
1322 if let Some(query) = self.query.take() {
1323 query.fail_result_limit();
1324 }
1325 }
1326
1327 pub fn fail_with_error(
1328 mut self,
1329 code: impl Into<String>,
1330 category: QueryTerminalErrorCategory,
1331 ) {
1332 if let Some(query) = self.query.take() {
1333 query.fail_with_error(code, category);
1334 }
1335 }
1336
1337 pub fn fail_serialization(mut self) {
1338 if let Some(query) = self.query.take() {
1339 query.fail_serialization();
1340 }
1341 }
1342
1343 pub fn into_query(mut self) -> RegisteredSqlQuery {
1344 self.query.take().expect("registered query guard consumed")
1345 }
1346}
1347
1348impl Drop for RegisteredQueryGuard {
1349 fn drop(&mut self) {
1350 if let Some(query) = self.query.take() {
1351 query.fail();
1352 }
1353 }
1354}
1355
1356fn validate_metadata(name: &str, value: Option<&str>) -> Result<()> {
1357 if value.is_some_and(|value| value.len() > MAX_METADATA_BYTES) {
1358 return Err(MongrelQueryError::Core(
1359 mongreldb_core::MongrelError::InvalidArgument(format!(
1360 "{name} exceeds {MAX_METADATA_BYTES} bytes"
1361 )),
1362 ));
1363 }
1364 Ok(())
1365}
1366
1367#[cfg(test)]
1368mod tests {
1369 use super::*;
1370
1371 #[test]
1372 fn query_ids_are_random_strict_and_round_trip() {
1373 let first = QueryId::random().unwrap();
1374 let second = QueryId::random().unwrap();
1375 assert_ne!(first, second);
1376 assert_eq!(first.to_string().parse::<QueryId>().unwrap(), first);
1377 assert!("abc".parse::<QueryId>().is_err());
1378 assert!("0000000000000000000000000000000z"
1379 .parse::<QueryId>()
1380 .is_err());
1381 }
1382
1383 #[test]
1384 fn unrepresentable_timeout_is_rejected_without_panicking() {
1385 let registry = Arc::new(SqlQueryRegistry::default());
1386 assert!(matches!(
1387 registry.register(SqlQueryOptions {
1388 timeout: Some(Duration::MAX),
1389 ..SqlQueryOptions::default()
1390 }),
1391 Err(MongrelQueryError::Core(
1392 mongreldb_core::MongrelError::InvalidArgument(_)
1393 ))
1394 ));
1395 assert_eq!(registry.active_count(), 0);
1396 }
1397
1398 #[test]
1399 fn active_and_retained_query_ids_are_rejected() {
1400 let registry = Arc::new(SqlQueryRegistry::new(1, 1, 1024, Duration::from_secs(60)));
1401 let id = QueryId::random().unwrap();
1402 let query = registry
1403 .register(SqlQueryOptions {
1404 query_id: Some(id),
1405 ..SqlQueryOptions::default()
1406 })
1407 .unwrap();
1408 assert!(matches!(
1409 registry.register(SqlQueryOptions {
1410 query_id: Some(id),
1411 ..SqlQueryOptions::default()
1412 }),
1413 Err(MongrelQueryError::QueryIdConflict { .. })
1414 ));
1415 query.complete().unwrap();
1416 assert_eq!(registry.active_count(), 0);
1417 assert_eq!(registry.finished_count(), 1);
1418 assert!(matches!(
1419 registry.register(SqlQueryOptions {
1420 query_id: Some(id),
1421 ..SqlQueryOptions::default()
1422 }),
1423 Err(MongrelQueryError::QueryIdConflict { .. })
1424 ));
1425 assert_eq!(registry.cancel(id), CancelOutcome::AlreadyFinished);
1426 }
1427
1428 #[test]
1429 fn query_id_can_be_reused_after_its_tombstone_expires() {
1430 let registry = Arc::new(SqlQueryRegistry::new(1, 1, 1024, Duration::ZERO));
1431 let id = QueryId::random().unwrap();
1432 registry
1433 .register(SqlQueryOptions {
1434 query_id: Some(id),
1435 ..SqlQueryOptions::default()
1436 })
1437 .unwrap()
1438 .complete()
1439 .unwrap();
1440
1441 let replacement = registry
1442 .register(SqlQueryOptions {
1443 query_id: Some(id),
1444 ..SqlQueryOptions::default()
1445 })
1446 .unwrap();
1447 assert_eq!(registry.status(id).unwrap().phase, SqlQueryPhase::Queued);
1448 replacement.complete().unwrap();
1449 }
1450
1451 #[test]
1452 fn status_capacity_eviction_does_not_release_query_id() {
1453 for (max_finished, max_finished_bytes) in [(1, usize::MAX), (10, 1)] {
1454 let owner = "o".repeat(MAX_METADATA_BYTES);
1455 let session_id = "s".repeat(MAX_METADATA_BYTES);
1456 let registry = Arc::new(SqlQueryRegistry::new(
1457 1,
1458 max_finished,
1459 max_finished_bytes,
1460 Duration::from_secs(60),
1461 ));
1462 let first_id = QueryId::random().unwrap();
1463 registry
1464 .register(SqlQueryOptions {
1465 query_id: Some(first_id),
1466 owner: Some(owner.clone()),
1467 session_id: Some(session_id.clone()),
1468 ..SqlQueryOptions::default()
1469 })
1470 .unwrap()
1471 .complete()
1472 .unwrap();
1473 registry
1474 .register(SqlQueryOptions::default())
1475 .unwrap()
1476 .complete()
1477 .unwrap();
1478
1479 assert!(registry.status(first_id).is_none());
1480 assert_eq!(
1481 registry.compact_finished_identity(first_id),
1482 Some((Some(owner), Some(session_id)))
1483 );
1484 assert_eq!(registry.cancel(first_id), CancelOutcome::AlreadyFinished);
1485 assert!(matches!(
1486 registry.register(SqlQueryOptions {
1487 query_id: Some(first_id),
1488 ..SqlQueryOptions::default()
1489 }),
1490 Err(MongrelQueryError::QueryIdConflict { .. })
1491 ));
1492 }
1493 }
1494
1495 #[test]
1496 fn compact_id_reservations_fail_closed_at_bounded_capacity() {
1497 let registry = Arc::new(SqlQueryRegistry::new(1, 0, 0, Duration::from_secs(60)));
1498 let first_id = QueryId::random().unwrap();
1499 registry
1500 .register(SqlQueryOptions {
1501 query_id: Some(first_id),
1502 ..SqlQueryOptions::default()
1503 })
1504 .unwrap()
1505 .complete()
1506 .unwrap();
1507 registry
1508 .register(SqlQueryOptions::default())
1509 .unwrap()
1510 .complete()
1511 .unwrap();
1512
1513 assert_eq!(registry.finished_count(), 0);
1514 assert_eq!(registry.cancel(first_id), CancelOutcome::AlreadyFinished);
1515 assert!(matches!(
1516 registry.register(SqlQueryOptions::default()),
1517 Err(MongrelQueryError::QueryRegistryFull)
1518 ));
1519 assert!(registry.approximate_bytes() <= 2 * APPROXIMATE_RESERVED_ID_BYTES);
1520 }
1521
1522 #[test]
1523 fn compact_identity_accounting_covers_maximum_metadata() {
1524 let registry = Arc::new(SqlQueryRegistry::new(1, 0, 0, Duration::from_secs(60)));
1525 let owner = "o".repeat(MAX_METADATA_BYTES);
1526 let session_id = "s".repeat(MAX_METADATA_BYTES);
1527 let query_id = QueryId::random().unwrap();
1528 registry
1529 .register(SqlQueryOptions {
1530 query_id: Some(query_id),
1531 owner: Some(owner.clone()),
1532 session_id: Some(session_id.clone()),
1533 ..SqlQueryOptions::default()
1534 })
1535 .unwrap()
1536 .complete()
1537 .unwrap();
1538
1539 assert_eq!(
1540 registry.compact_finished_identity(query_id),
1541 Some((Some(owner), Some(session_id)))
1542 );
1543 assert!(registry.approximate_bytes() <= APPROXIMATE_RESERVED_ID_BYTES);
1544 }
1545
1546 #[test]
1547 fn cancel_and_commit_fence_have_one_winner() {
1548 for cancel_first in [true, false] {
1549 let registry = Arc::new(SqlQueryRegistry::default());
1550 let query = registry.register(SqlQueryOptions::default()).unwrap();
1551 query
1552 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1553 .unwrap();
1554 if cancel_first {
1555 assert_eq!(
1556 query.request_cancel(CancellationReason::ClientRequest),
1557 CancelOutcome::Accepted
1558 );
1559 assert!(query.enter_commit_critical().is_err());
1560 let status = query.status();
1561 assert_eq!(
1562 status.cancel_requested_phase,
1563 Some(SqlQueryPhase::Executing)
1564 );
1565 assert_eq!(status.cancel_observed_phase, Some(SqlQueryPhase::Executing));
1566 assert_eq!(status.commit_fence_outcome, CommitFenceOutcome::CancelWon);
1567 } else {
1568 query.enter_commit_critical().unwrap();
1569 assert_eq!(
1570 query.request_cancel(CancellationReason::ClientRequest),
1571 CancelOutcome::TooLate
1572 );
1573 query.record_commit(0, 7);
1574 query.complete().unwrap();
1575 let status = registry.status(query.id()).unwrap();
1576 assert!(status.committed);
1577 assert_eq!(status.commit_fence_outcome, CommitFenceOutcome::CommitWon);
1578 }
1579 }
1580 }
1581
1582 #[test]
1583 fn cancel_and_terminal_completion_have_one_winner() {
1584 for _ in 0..100 {
1585 let registry = Arc::new(SqlQueryRegistry::default());
1586 let query = registry.register(SqlQueryOptions::default()).unwrap();
1587 query
1588 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1589 .unwrap();
1590 query.begin_serialization().unwrap();
1591
1592 let barrier = Arc::new(std::sync::Barrier::new(3));
1593 let cancel_query = query.clone();
1594 let cancel_barrier = Arc::clone(&barrier);
1595 let cancel = std::thread::spawn(move || {
1596 cancel_barrier.wait();
1597 cancel_query.request_cancel(CancellationReason::ClientRequest)
1598 });
1599 let complete_query = query.clone();
1600 let complete_barrier = Arc::clone(&barrier);
1601 let complete = std::thread::spawn(move || {
1602 complete_barrier.wait();
1603 complete_query.try_complete()
1604 });
1605 barrier.wait();
1606
1607 let cancel = cancel.join().unwrap();
1608 let complete = complete.join().unwrap();
1609 let status = registry.status(query.id()).unwrap();
1610 match cancel {
1611 CancelOutcome::Accepted => {
1612 assert!(matches!(
1613 complete,
1614 Err(MongrelQueryError::QueryCancelled { .. })
1615 ));
1616 assert_eq!(status.phase, SqlQueryPhase::Cancelled);
1617 }
1618 CancelOutcome::AlreadyFinished => {
1619 complete.unwrap();
1620 assert_eq!(status.phase, SqlQueryPhase::Completed);
1621 }
1622 other => panic!("unexpected cancel outcome {other:?}"),
1623 }
1624 }
1625 }
1626
1627 #[test]
1628 fn externally_claimed_completion_is_finalized_and_retained() {
1629 let registry = Arc::new(SqlQueryRegistry::default());
1630 let query = registry.register(SqlQueryOptions::default()).unwrap();
1631 query
1632 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1633 .unwrap();
1634 query.begin_serialization().unwrap();
1635 query
1636 .transition(SqlQueryPhase::Serializing, SqlQueryPhase::Completed)
1637 .unwrap();
1638
1639 query.try_complete().unwrap();
1640
1641 assert_eq!(registry.active_count(), 0);
1642 let status = registry.status(query.id()).unwrap();
1643 assert_eq!(status.phase, SqlQueryPhase::Completed);
1644 assert_eq!(
1645 status.serialization_outcome,
1646 SerializationOutcome::Succeeded
1647 );
1648 }
1649
1650 #[test]
1651 fn zero_batch_completion_checks_expired_deadline() {
1652 let registry = Arc::new(SqlQueryRegistry::default());
1653 let query = registry
1654 .register(SqlQueryOptions {
1655 timeout: Some(Duration::from_millis(1)),
1656 ..SqlQueryOptions::default()
1657 })
1658 .unwrap();
1659 query
1660 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1661 .unwrap();
1662 query.begin_serialization().unwrap();
1663 std::thread::sleep(Duration::from_millis(5));
1664
1665 assert!(matches!(
1666 query.try_complete(),
1667 Err(MongrelQueryError::DeadlineExceeded { .. })
1668 ));
1669 let status = registry.status(query.id()).unwrap();
1670 assert_eq!(status.phase, SqlQueryPhase::Cancelled);
1671 assert_eq!(
1672 status.terminal_state(),
1673 Some(QueryTerminalState::DeadlineBeforeCommit)
1674 );
1675 }
1676
1677 #[test]
1678 fn parent_cancellation_wins_terminal_completion() {
1679 let registry = Arc::new(SqlQueryRegistry::default());
1680 let parent = ExecutionControl::new(None);
1681 let query = registry
1682 .register(SqlQueryOptions {
1683 parent_control: Some(parent.clone()),
1684 ..SqlQueryOptions::default()
1685 })
1686 .unwrap();
1687 query
1688 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1689 .unwrap();
1690 query.begin_serialization().unwrap();
1691 parent.cancel(CancellationReason::SessionClosed);
1692
1693 assert!(matches!(
1694 query.try_complete(),
1695 Err(MongrelQueryError::QueryCancelled {
1696 reason: CancellationReason::SessionClosed,
1697 ..
1698 })
1699 ));
1700 assert_eq!(
1701 registry.status(query.id()).unwrap().phase,
1702 SqlQueryPhase::Cancelled
1703 );
1704 }
1705
1706 #[test]
1707 fn explicit_serialization_failure_beats_passive_parent_cancel() {
1708 let registry = Arc::new(SqlQueryRegistry::default());
1709 let parent = ExecutionControl::new(None);
1710 let query = registry
1711 .register(SqlQueryOptions {
1712 parent_control: Some(parent.clone()),
1713 ..SqlQueryOptions::default()
1714 })
1715 .unwrap();
1716 query
1717 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1718 .unwrap();
1719 query.begin_serialization().unwrap();
1720 parent.cancel(CancellationReason::SessionClosed);
1721 query.fail_serialization();
1722
1723 let status = registry.status(query.id()).unwrap();
1724 assert_eq!(status.phase, SqlQueryPhase::Failed);
1725 assert_eq!(status.terminal_error.unwrap().code, "SERIALIZATION_FAILED");
1726 }
1727
1728 #[test]
1729 fn serialization_failure_after_commit_keeps_exact_durable_outcome() {
1730 let registry = Arc::new(SqlQueryRegistry::default());
1731 let query = registry.register(SqlQueryOptions::default()).unwrap();
1732 query
1733 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1734 .unwrap();
1735 query.begin_statement(2);
1736 query.enter_commit_critical().unwrap();
1737 query.record_commit(2, 73);
1738 query.exit_commit_critical().unwrap();
1739 query.begin_serialization().unwrap();
1740 query.fail_serialization();
1741
1742 let status = registry.status(query.id()).unwrap();
1743 assert_eq!(status.phase, SqlQueryPhase::Failed);
1744 assert_eq!(status.durable_outcome.last_commit_epoch, Some(73));
1745 assert_eq!(status.durable_outcome.committed_statements, 1);
1746 assert_eq!(
1747 status.terminal_error.unwrap().code,
1748 "SERIALIZATION_FAILED_AFTER_COMMIT"
1749 );
1750 assert!(matches!(
1751 query.commit_outcome_error("encode failed"),
1752 MongrelQueryError::CommitOutcome {
1753 committed: true,
1754 committed_statements: 1,
1755 last_commit_epoch: Some(73),
1756 first_commit_statement_index: Some(2),
1757 last_commit_statement_index: Some(2),
1758 statement_index: 2,
1759 ..
1760 }
1761 ));
1762 }
1763
1764 #[test]
1765 fn guard_cleans_up_dropped_execution_without_raw_sql() {
1766 let registry = Arc::new(SqlQueryRegistry::default());
1767 let query = registry
1768 .register(SqlQueryOptions {
1769 owner: Some("alice".into()),
1770 session_id: Some("session".into()),
1771 ..SqlQueryOptions::default()
1772 })
1773 .unwrap();
1774 query.set_sql_metadata("SELECT secret FROM docs WHERE token = 'private'");
1775 let id = query.id();
1776 drop(RegisteredQueryGuard::new(query));
1777 let status = registry.status(id).unwrap();
1778 assert_eq!(status.phase, SqlQueryPhase::Failed);
1779 assert_eq!(status.operation, "SELECT");
1780 assert_ne!(status.sql_fingerprint, [0; 32]);
1781 }
1782
1783 #[test]
1784 fn sql_operation_metadata_is_bounded() {
1785 let registry = Arc::new(SqlQueryRegistry::default());
1786 let query = registry.register(SqlQueryOptions::default()).unwrap();
1787 let id = query.id();
1788 query.set_sql_metadata(&format!("{} value", "x".repeat(4096)));
1789 drop(RegisteredQueryGuard::new(query));
1790 assert_eq!(registry.status(id).unwrap().operation.len(), 64);
1791 }
1792
1793 #[test]
1794 fn tombstone_keeps_durable_and_terminal_outcomes() {
1795 let registry = Arc::new(SqlQueryRegistry::default());
1796 let query = registry.register(SqlQueryOptions::default()).unwrap();
1797 let id = query.id();
1798 query
1799 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1800 .unwrap();
1801 query.begin_statement(0);
1802 query.enter_commit_critical().unwrap();
1803 query.record_commit(0, 41);
1804 query.record_commit(0, 41);
1805 query.exit_commit_critical().unwrap();
1806 query.complete_statement(0);
1807 query.begin_statement(1);
1808 query.begin_serialization().unwrap();
1809 let active_outcome = query.status().durable_outcome;
1810
1811 assert_eq!(
1812 query.request_cancel(CancellationReason::ClientRequest),
1813 CancelOutcome::Accepted
1814 );
1815 query.fail();
1816
1817 let status = registry.status(id).unwrap();
1818 assert_eq!(status.durable_outcome, active_outcome);
1819 assert_eq!(
1820 status.durable_outcome,
1821 DurableOutcome {
1822 committed: true,
1823 committed_statements: 1,
1824 last_commit_epoch: Some(41),
1825 first_commit_statement_index: Some(0),
1826 last_commit_statement_index: Some(0),
1827 }
1828 );
1829 assert_eq!(status.serialization_outcome, SerializationOutcome::Failed);
1830 assert_eq!(status.commit_fence_outcome, CommitFenceOutcome::CommitWon);
1831 assert_eq!(
1832 status.terminal_error,
1833 Some(QueryTerminalError {
1834 code: "QUERY_CANCELLED_AFTER_COMMIT".into(),
1835 category: QueryTerminalErrorCategory::Cancellation,
1836 })
1837 );
1838 assert_eq!(
1839 status.terminal_state(),
1840 Some(QueryTerminalState::CancelledAfterCommit)
1841 );
1842 }
1843
1844 #[test]
1845 fn cancellation_error_carries_durable_receipt() {
1846 let registry = Arc::new(SqlQueryRegistry::default());
1847 let query = registry.register(SqlQueryOptions::default()).unwrap();
1848 query
1849 .transition(SqlQueryPhase::Queued, SqlQueryPhase::Executing)
1850 .unwrap();
1851 query.begin_statement(0);
1852 query.enter_commit_critical().unwrap();
1853 query.record_commit(0, 73);
1854 query.exit_commit_critical().unwrap();
1855 query.complete_statement(0);
1856 query.begin_statement(1);
1857 query.begin_serialization().unwrap();
1858 assert_eq!(
1859 query.request_cancel(CancellationReason::ClientRequest),
1860 CancelOutcome::Accepted
1861 );
1862
1863 assert!(matches!(
1864 query.checkpoint(),
1865 Err(MongrelQueryError::QueryCancelled {
1866 committed: true,
1867 committed_statements: 1,
1868 last_commit_epoch: Some(73),
1869 first_commit_statement_index: Some(0),
1870 last_commit_statement_index: Some(0),
1871 completed_statements: 1,
1872 cancelled_statement_index: 1,
1873 ..
1874 })
1875 ));
1876 query.fail();
1877 }
1878
1879 #[test]
1880 fn explicit_terminal_error_category_survives_tombstone() {
1881 let registry = Arc::new(SqlQueryRegistry::default());
1882 let query = registry.register(SqlQueryOptions::default()).unwrap();
1883 let id = query.id();
1884 query.record_terminal_error(
1885 "RESULT_LIMIT_EXCEEDED",
1886 QueryTerminalErrorCategory::ResultLimit,
1887 );
1888 query.fail();
1889
1890 let status = registry.status(id).unwrap();
1891 assert_eq!(
1892 status.terminal_error,
1893 Some(QueryTerminalError {
1894 code: "RESULT_LIMIT_EXCEEDED".into(),
1895 category: QueryTerminalErrorCategory::ResultLimit,
1896 })
1897 );
1898 assert_eq!(
1899 status.terminal_state(),
1900 Some(QueryTerminalState::FailedBeforeCommit)
1901 );
1902 }
1903
1904 #[test]
1905 fn serialization_failure_code_records_commit_outcome() {
1906 let registry = Arc::new(SqlQueryRegistry::default());
1907 let query = registry.register(SqlQueryOptions::default()).unwrap();
1908 let id = query.id();
1909 query.record_commit(0, 7);
1910 query.record_serialization_failure("SERIALIZATION_FAILED");
1911 query.fail();
1912
1913 let status = registry.status(id).unwrap();
1914 assert_eq!(
1915 status.terminal_error.as_ref().unwrap().code,
1916 "SERIALIZATION_FAILED_AFTER_COMMIT"
1917 );
1918 assert_eq!(
1919 status.terminal_state(),
1920 Some(QueryTerminalState::CommittedWithError)
1921 );
1922 }
1923}