Skip to main content

catalog/
process_manager.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16use std::collections::hash_map::Entry;
17use std::fmt::{Debug, Display, Formatter};
18use std::sync::atomic::{AtomicU32, Ordering};
19use std::sync::{Arc, RwLock};
20use std::time::{Duration, Instant, UNIX_EPOCH};
21
22use api::v1::frontend::{KillProcessRequest, ListProcessRequest, ProcessInfo};
23use common_base::cancellation::CancellationHandle;
24use common_event_recorder::EventRecorderRef;
25use common_frontend::selector::{FrontendSelector, MetaClientSelector};
26use common_frontend::slow_query_event::SlowQueryEvent;
27use common_telemetry::logging::SlowQueriesRecordType;
28use common_telemetry::{debug, info, slow};
29use common_time::util::current_time_millis;
30use meta_client::MetaClientRef;
31use promql_parser::parser::EvalStmt;
32use rand::random;
33use snafu::{OptionExt, ResultExt, ensure};
34use sql::statements::statement::Statement;
35
36use crate::error;
37use crate::metrics::{PROCESS_KILL_COUNT, PROCESS_LIST_COUNT};
38
39pub type ProcessId = u32;
40pub type ProcessManagerRef = Arc<ProcessManager>;
41
42/// Query process manager.
43pub struct ProcessManager {
44    /// Address this frontend advertises to the cluster.
45    ///
46    /// It is the heartbeat peer address, which is also the only address other
47    /// frontends use to reach this node's process management service. The same
48    /// value identifies processes owned by this frontend and routes kill
49    /// requests, so discovery addresses and process addresses share one
50    /// namespace.
51    peer_addr: String,
52    /// Next process id for local queries.
53    next_id: AtomicU32,
54    /// Running process per catalog.
55    catalogs: RwLock<HashMap<String, HashMap<ProcessId, CancellableProcess>>>,
56    /// Frontend selector to locate frontend nodes.
57    frontend_selector: Option<MetaClientSelector>,
58}
59
60/// Represents a parsed query statement, functionally equivalent to [query::parser::QueryStatement].
61/// This enum is defined here to avoid cyclic dependencies with the query parser module.
62#[derive(Debug, Clone)]
63pub enum QueryStatement {
64    Sql(Statement),
65    // The optional string is the alias of the PromQL query.
66    Promql(EvalStmt, Option<String>),
67    /// Logical plan with original query string
68    Plan(String),
69}
70
71impl Display for QueryStatement {
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        match self {
74            QueryStatement::Sql(stmt) => write!(f, "{}", stmt),
75            QueryStatement::Promql(eval_stmt, alias) => {
76                if let Some(alias) = alias {
77                    write!(f, "{} AS {}", eval_stmt, alias)
78                } else {
79                    write!(f, "{}", eval_stmt)
80                }
81            }
82            QueryStatement::Plan(query) => write!(f, "{}", query),
83        }
84    }
85}
86
87impl ProcessManager {
88    /// Creates a [ProcessManager] for the frontend identified by `peer_addr`.
89    ///
90    /// `peer_addr` must be the address the frontend advertises to metasrv, not
91    /// its public gRPC address: discovered peers are matched by that address to
92    /// exclude this node from remote listings and to route kill requests.
93    pub fn new(peer_addr: String, meta_client: Option<MetaClientRef>) -> Self {
94        let frontend_selector = meta_client.map(MetaClientSelector::new);
95        Self {
96            peer_addr,
97            next_id: Default::default(),
98            catalogs: Default::default(),
99            frontend_selector,
100        }
101    }
102}
103
104impl ProcessManager {
105    /// Registers a submitted query. Use the provided id if present.
106    #[must_use]
107    pub fn register_query(
108        self: &Arc<Self>,
109        catalog: String,
110        schemas: Vec<String>,
111        query: String,
112        client: String,
113        query_id: Option<ProcessId>,
114        _slow_query_timer: Option<SlowQueryTimer>,
115    ) -> Ticket {
116        let id = query_id.unwrap_or_else(|| self.next_id.fetch_add(1, Ordering::Relaxed));
117        let process = ProcessInfo {
118            id,
119            catalog: catalog.clone(),
120            schemas,
121            query,
122            start_timestamp: current_time_millis(),
123            client,
124            frontend: self.peer_addr.clone(),
125        };
126        let cancellation_handle = Arc::new(CancellationHandle::default());
127        let cancellable_process = CancellableProcess::new(cancellation_handle.clone(), process);
128
129        self.catalogs
130            .write()
131            .unwrap()
132            .entry(catalog.clone())
133            .or_default()
134            .insert(id, cancellable_process);
135
136        Ticket {
137            catalog,
138            manager: self.clone(),
139            id,
140            cancellation_handle,
141            _slow_query_timer,
142        }
143    }
144
145    /// Generates the next process id.
146    pub fn next_id(&self) -> u32 {
147        self.next_id.fetch_add(1, Ordering::Relaxed)
148    }
149
150    /// De-register a query from process list.
151    pub fn deregister_query(&self, catalog: String, id: ProcessId) {
152        if let Entry::Occupied(mut o) = self.catalogs.write().unwrap().entry(catalog) {
153            let process = o.get_mut().remove(&id);
154            debug!("Deregister process: {:?}", process);
155            if o.get().is_empty() {
156                o.remove();
157            }
158        }
159    }
160
161    /// List local running processes in given catalog.
162    pub fn local_processes(&self, catalog: Option<&str>) -> error::Result<Vec<ProcessInfo>> {
163        let catalogs = self.catalogs.read().unwrap();
164        let result = if let Some(catalog) = catalog {
165            if let Some(catalogs) = catalogs.get(catalog) {
166                catalogs.values().map(|p| p.process.clone()).collect()
167            } else {
168                vec![]
169            }
170        } else {
171            catalogs
172                .values()
173                .flat_map(|v| v.values().map(|p| p.process.clone()))
174                .collect()
175        };
176        Ok(result)
177    }
178
179    /// Lists running processes from every selected frontend and the local registry.
180    ///
181    /// The local frontend is excluded from the remote selection by its advertised
182    /// peer address, which is what [`MetaClientSelector`] discovers. Its processes
183    /// are appended from the local registry exactly once.
184    ///
185    /// Returns an error if a remote frontend cannot provide its process list.
186    pub async fn list_all_processes(
187        &self,
188        catalog: Option<&str>,
189    ) -> error::Result<Vec<ProcessInfo>> {
190        let mut processes = vec![];
191        if let Some(remote_frontend_selector) = self.frontend_selector.as_ref() {
192            let frontends = remote_frontend_selector
193                .select(|peer| peer.addr != self.peer_addr)
194                .await
195                .context(error::InvokeFrontendSnafu)?;
196            for mut f in frontends {
197                let resp = f
198                    .list_process(ListProcessRequest {
199                        catalog: catalog.unwrap_or_default().to_string(),
200                    })
201                    .await
202                    .context(error::InvokeFrontendSnafu)?;
203                processes.extend(resp.processes);
204            }
205        }
206        processes.extend(self.local_processes(catalog)?);
207        Ok(processes)
208    }
209
210    /// Kills query with provided catalog and id.
211    ///
212    /// `peer_addr` is the frontend address from the process id, which is the
213    /// advertised peer address of the frontend that owns the process.
214    pub async fn kill_process(
215        &self,
216        peer_addr: String,
217        catalog: String,
218        id: ProcessId,
219    ) -> error::Result<bool> {
220        if peer_addr == self.peer_addr {
221            self.kill_local_process(catalog, id).await
222        } else {
223            let mut nodes = self
224                .frontend_selector
225                .as_ref()
226                .context(error::MetaClientMissingSnafu)?
227                .select(|peer| peer.addr == peer_addr)
228                .await
229                .context(error::InvokeFrontendSnafu)?;
230            ensure!(
231                !nodes.is_empty(),
232                error::FrontendNotFoundSnafu { addr: peer_addr }
233            );
234
235            let request = KillProcessRequest {
236                server_addr: peer_addr,
237                catalog,
238                process_id: id,
239            };
240            nodes[0]
241                .kill_process(request)
242                .await
243                .context(error::InvokeFrontendSnafu)?;
244            Ok(true)
245        }
246    }
247
248    /// Kills local query with provided catalog and id.
249    pub async fn kill_local_process(&self, catalog: String, id: ProcessId) -> error::Result<bool> {
250        if let Some(catalogs) = self.catalogs.write().unwrap().get_mut(&catalog) {
251            if let Some(process) = catalogs.remove(&id) {
252                process.handle.cancel();
253                info!(
254                    "Killed process, catalog: {}, id: {:?}",
255                    process.process.catalog, process.process.id
256                );
257                PROCESS_KILL_COUNT.with_label_values(&[&catalog]).inc();
258                Ok(true)
259            } else {
260                debug!("Failed to kill process, id not found: {}", id);
261                Ok(false)
262            }
263        } else {
264            debug!("Failed to kill process, catalog not found: {}", catalog);
265            Ok(false)
266        }
267    }
268}
269
270pub struct Ticket {
271    pub(crate) catalog: String,
272    pub(crate) manager: ProcessManagerRef,
273    pub(crate) id: ProcessId,
274    pub cancellation_handle: Arc<CancellationHandle>,
275
276    // Keep the handle of the slow query timer to ensure it will trigger the event recording when dropped.
277    _slow_query_timer: Option<SlowQueryTimer>,
278}
279
280impl Drop for Ticket {
281    fn drop(&mut self) {
282        self.manager
283            .deregister_query(std::mem::take(&mut self.catalog), self.id);
284    }
285}
286
287struct CancellableProcess {
288    handle: Arc<CancellationHandle>,
289    process: ProcessInfo,
290}
291
292impl Drop for CancellableProcess {
293    fn drop(&mut self) {
294        PROCESS_LIST_COUNT
295            .with_label_values(&[&self.process.catalog])
296            .dec();
297    }
298}
299
300impl CancellableProcess {
301    fn new(handle: Arc<CancellationHandle>, process: ProcessInfo) -> Self {
302        PROCESS_LIST_COUNT
303            .with_label_values(&[&process.catalog])
304            .inc();
305        Self { handle, process }
306    }
307}
308
309impl Debug for CancellableProcess {
310    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
311        f.debug_struct("CancellableProcess")
312            .field("cancelled", &self.handle.is_cancelled())
313            .field("process", &self.process)
314            .finish()
315    }
316}
317
318/// SlowQueryTimer is used to log slow query when it's dropped.
319/// In drop(), it will check if the query is slow and send the slow query event to the handler.
320pub struct SlowQueryTimer {
321    start: Instant,
322    stmt: QueryStatement,
323    schema_name: String,
324    threshold: Duration,
325    sample_ratio: f64,
326    record_type: SlowQueriesRecordType,
327    recorder: EventRecorderRef,
328    record_state: Arc<RwLock<SlowQueryRecordState>>,
329}
330
331#[derive(Default)]
332struct SlowQueryRecordState {
333    force_record: bool,
334    payload: serde_json::Value,
335}
336
337/// A handle to attach diagnostics to a slow query and force it to be recorded.
338#[derive(Clone)]
339pub struct SlowQueryRecorder {
340    record_state: Arc<RwLock<SlowQueryRecordState>>,
341}
342
343impl SlowQueryRecorder {
344    /// Attaches the payload and marks the query to bypass the threshold and sampling checks.
345    pub fn force_record_with_payload(&self, payload: serde_json::Value) {
346        let mut state = self.record_state.write().unwrap();
347        state.payload = payload;
348        state.force_record = true;
349    }
350}
351
352impl SlowQueryTimer {
353    pub fn new(
354        stmt: QueryStatement,
355        schema_name: String,
356        threshold: Duration,
357        sample_ratio: f64,
358        record_type: SlowQueriesRecordType,
359        recorder: EventRecorderRef,
360    ) -> Self {
361        Self {
362            start: Instant::now(),
363            stmt,
364            schema_name,
365            threshold,
366            sample_ratio,
367            record_type,
368            recorder,
369            record_state: Arc::default(),
370        }
371    }
372
373    /// Returns a handle that can attach diagnostics and force this query to be recorded.
374    pub fn recorder(&self) -> SlowQueryRecorder {
375        SlowQueryRecorder {
376            record_state: self.record_state.clone(),
377        }
378    }
379}
380
381impl SlowQueryTimer {
382    fn send_slow_query_event(&self, elapsed: Duration, payload: serde_json::Value) {
383        let mut slow_query_event = SlowQueryEvent {
384            cost: elapsed.as_millis() as u64,
385            threshold: self.threshold.as_millis() as u64,
386            query: "".to_string(),
387            schema_name: self.schema_name.clone(),
388
389            // The following fields are only used for PromQL queries.
390            is_promql: false,
391            promql_range: None,
392            promql_step: None,
393            promql_start: None,
394            promql_end: None,
395            payload,
396        };
397
398        match &self.stmt {
399            QueryStatement::Promql(stmt, _alias) => {
400                slow_query_event.is_promql = true;
401                slow_query_event.query = self.stmt.to_string();
402                slow_query_event.promql_step = Some(stmt.interval.as_millis() as u64);
403
404                let start = stmt
405                    .start
406                    .duration_since(UNIX_EPOCH)
407                    .unwrap_or_default()
408                    .as_millis() as i64;
409
410                let end = stmt
411                    .end
412                    .duration_since(UNIX_EPOCH)
413                    .unwrap_or_default()
414                    .as_millis() as i64;
415
416                slow_query_event.promql_range = Some((end - start) as u64);
417                slow_query_event.promql_start = Some(start);
418                slow_query_event.promql_end = Some(end);
419            }
420            QueryStatement::Sql(stmt) => {
421                slow_query_event.query = stmt.to_string();
422            }
423            QueryStatement::Plan(query) => {
424                slow_query_event.query = query.clone();
425            }
426        }
427
428        match self.record_type {
429            // Send the slow query event to the event recorder to persist it as the system table.
430            SlowQueriesRecordType::SystemTable => {
431                self.recorder.record(Box::new(slow_query_event));
432            }
433            // Record the slow query through its dedicated logger.
434            SlowQueriesRecordType::Log => {
435                slow!(
436                    cost = slow_query_event.cost,
437                    threshold = slow_query_event.threshold,
438                    query = slow_query_event.query,
439                    schema_name = slow_query_event.schema_name,
440                    is_promql = slow_query_event.is_promql,
441                    promql_range = slow_query_event.promql_range,
442                    promql_step = slow_query_event.promql_step,
443                    promql_start = slow_query_event.promql_start,
444                    promql_end = slow_query_event.promql_end,
445                    payload = slow_query_event.payload.to_string(),
446                );
447            }
448        }
449    }
450}
451
452impl Drop for SlowQueryTimer {
453    fn drop(&mut self) {
454        let elapsed = self.start.elapsed();
455        let (force_record, payload) = {
456            let state = self.record_state.read().unwrap();
457            (state.force_record, state.payload.clone())
458        };
459
460        if force_record
461            || (elapsed > self.threshold
462                && (self.sample_ratio >= 1.0 || random::<f64>() <= self.sample_ratio))
463        {
464            self.send_slow_query_event(elapsed, payload);
465        }
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use std::sync::{Arc, Mutex};
472    use std::time::Duration;
473
474    use common_event_recorder::{Event, EventRecorder, EventTypeFilter, EventTypeFilterRef};
475    use common_frontend::slow_query_event::SlowQueryEvent;
476    use common_telemetry::logging::SlowQueriesRecordType;
477    use serde_json::Value;
478
479    use crate::process_manager::{ProcessManager, QueryStatement, SlowQueryTimer};
480
481    #[derive(Debug, Default)]
482    struct RecordingEventRecorder {
483        events: Mutex<Vec<(String, Value)>>,
484    }
485
486    impl EventRecorder for RecordingEventRecorder {
487        fn record(&self, event: Box<dyn Event>) {
488            let event = event
489                .as_any()
490                .downcast_ref::<SlowQueryEvent>()
491                .expect("expected a slow query event");
492            self.events
493                .lock()
494                .unwrap()
495                .push((event.query.clone(), event.payload.clone()));
496        }
497
498        fn event_type_filter(&self) -> EventTypeFilterRef {
499            Arc::new(EventTypeFilter::All)
500        }
501
502        fn close(&self) {}
503    }
504
505    #[test]
506    fn test_forced_slow_query_bypasses_threshold_and_sampling() {
507        let event_recorder = Arc::new(RecordingEventRecorder::default());
508        let timer = SlowQueryTimer::new(
509            QueryStatement::Plan("EXPLAIN ANALYZE VERBOSE SELECT 1".to_string()),
510            "public".to_string(),
511            Duration::from_secs(3600),
512            0.0,
513            SlowQueriesRecordType::SystemTable,
514            event_recorder.clone(),
515        );
516        let payload = serde_json::json!({
517            "timed_out": true,
518            "metrics": [{"stage": 0}],
519        });
520        timer.recorder().force_record_with_payload(payload.clone());
521
522        drop(timer);
523
524        let events = event_recorder.events.lock().unwrap();
525        assert_eq!(events.len(), 1);
526        assert_eq!(events[0].0, "EXPLAIN ANALYZE VERBOSE SELECT 1");
527        assert_eq!(events[0].1, payload);
528    }
529
530    #[test]
531    fn test_unforced_fast_query_is_not_recorded() {
532        let event_recorder = Arc::new(RecordingEventRecorder::default());
533        let timer = SlowQueryTimer::new(
534            QueryStatement::Plan("SELECT 1".to_string()),
535            "public".to_string(),
536            Duration::from_secs(3600),
537            0.0,
538            SlowQueriesRecordType::SystemTable,
539            event_recorder.clone(),
540        );
541
542        drop(timer);
543
544        assert!(event_recorder.events.lock().unwrap().is_empty());
545    }
546
547    #[tokio::test]
548    async fn test_register_query() {
549        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
550        let ticket = process_manager.clone().register_query(
551            "public".to_string(),
552            vec!["test".to_string()],
553            "SELECT * FROM table".to_string(),
554            "".to_string(),
555            None,
556            None,
557        );
558
559        let running_processes = process_manager.local_processes(None).unwrap();
560        assert_eq!(running_processes.len(), 1);
561        assert_eq!(&running_processes[0].frontend, "127.0.0.1:8000");
562        assert_eq!(running_processes[0].id, ticket.id);
563        assert_eq!(&running_processes[0].query, "SELECT * FROM table");
564
565        drop(ticket);
566        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
567    }
568
569    #[tokio::test]
570    async fn test_register_query_with_custom_id() {
571        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
572        let custom_id = 12345;
573
574        let ticket = process_manager.clone().register_query(
575            "public".to_string(),
576            vec!["test".to_string()],
577            "SELECT * FROM table".to_string(),
578            "client1".to_string(),
579            Some(custom_id),
580            None,
581        );
582
583        assert_eq!(ticket.id, custom_id);
584
585        let running_processes = process_manager.local_processes(None).unwrap();
586        assert_eq!(running_processes.len(), 1);
587        assert_eq!(running_processes[0].id, custom_id);
588        assert_eq!(&running_processes[0].client, "client1");
589    }
590
591    #[tokio::test]
592    async fn test_multiple_queries_same_catalog() {
593        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
594
595        let ticket1 = process_manager.clone().register_query(
596            "public".to_string(),
597            vec!["schema1".to_string()],
598            "SELECT * FROM table1".to_string(),
599            "client1".to_string(),
600            None,
601            None,
602        );
603
604        let ticket2 = process_manager.clone().register_query(
605            "public".to_string(),
606            vec!["schema2".to_string()],
607            "SELECT * FROM table2".to_string(),
608            "client2".to_string(),
609            None,
610            None,
611        );
612
613        let running_processes = process_manager.local_processes(Some("public")).unwrap();
614        assert_eq!(running_processes.len(), 2);
615
616        // Verify both processes are present
617        let ids: Vec<u32> = running_processes.iter().map(|p| p.id).collect();
618        assert!(ids.contains(&ticket1.id));
619        assert!(ids.contains(&ticket2.id));
620    }
621
622    #[tokio::test]
623    async fn test_multiple_catalogs() {
624        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
625
626        let _ticket1 = process_manager.clone().register_query(
627            "catalog1".to_string(),
628            vec!["schema1".to_string()],
629            "SELECT * FROM table1".to_string(),
630            "client1".to_string(),
631            None,
632            None,
633        );
634
635        let _ticket2 = process_manager.clone().register_query(
636            "catalog2".to_string(),
637            vec!["schema2".to_string()],
638            "SELECT * FROM table2".to_string(),
639            "client2".to_string(),
640            None,
641            None,
642        );
643
644        // Test listing processes for specific catalog
645        let catalog1_processes = process_manager.local_processes(Some("catalog1")).unwrap();
646        assert_eq!(catalog1_processes.len(), 1);
647        assert_eq!(&catalog1_processes[0].catalog, "catalog1");
648
649        let catalog2_processes = process_manager.local_processes(Some("catalog2")).unwrap();
650        assert_eq!(catalog2_processes.len(), 1);
651        assert_eq!(&catalog2_processes[0].catalog, "catalog2");
652
653        // Test listing all processes
654        let all_processes = process_manager.local_processes(None).unwrap();
655        assert_eq!(all_processes.len(), 2);
656    }
657
658    #[tokio::test]
659    async fn test_deregister_query() {
660        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
661
662        let ticket = process_manager.clone().register_query(
663            "public".to_string(),
664            vec!["test".to_string()],
665            "SELECT * FROM table".to_string(),
666            "client1".to_string(),
667            None,
668            None,
669        );
670        assert_eq!(process_manager.local_processes(None).unwrap().len(), 1);
671        process_manager.deregister_query("public".to_string(), ticket.id);
672        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
673    }
674
675    #[tokio::test]
676    async fn test_cancellation_handle() {
677        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
678
679        let ticket = process_manager.clone().register_query(
680            "public".to_string(),
681            vec!["test".to_string()],
682            "SELECT * FROM table".to_string(),
683            "client1".to_string(),
684            None,
685            None,
686        );
687
688        assert!(!ticket.cancellation_handle.is_cancelled());
689        ticket.cancellation_handle.cancel();
690        assert!(ticket.cancellation_handle.is_cancelled());
691    }
692
693    #[tokio::test]
694    async fn test_kill_local_process() {
695        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
696
697        let ticket = process_manager.clone().register_query(
698            "public".to_string(),
699            vec!["test".to_string()],
700            "SELECT * FROM table".to_string(),
701            "client1".to_string(),
702            None,
703            None,
704        );
705        assert!(!ticket.cancellation_handle.is_cancelled());
706        let killed = process_manager
707            .kill_process(
708                "127.0.0.1:8000".to_string(),
709                "public".to_string(),
710                ticket.id,
711            )
712            .await
713            .unwrap();
714
715        assert!(killed);
716        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
717    }
718
719    #[tokio::test]
720    async fn test_kill_nonexistent_process() {
721        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
722        let killed = process_manager
723            .kill_process("127.0.0.1:8000".to_string(), "public".to_string(), 999)
724            .await
725            .unwrap();
726        assert!(!killed);
727    }
728
729    #[tokio::test]
730    async fn test_kill_process_nonexistent_catalog() {
731        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
732        let killed = process_manager
733            .kill_process("127.0.0.1:8000".to_string(), "nonexistent".to_string(), 1)
734            .await
735            .unwrap();
736        assert!(!killed);
737    }
738
739    #[tokio::test]
740    async fn test_process_info_fields() {
741        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
742
743        let _ticket = process_manager.clone().register_query(
744            "test_catalog".to_string(),
745            vec!["schema1".to_string(), "schema2".to_string()],
746            "SELECT COUNT(*) FROM users WHERE age > 18".to_string(),
747            "test_client".to_string(),
748            Some(42),
749            None,
750        );
751
752        let processes = process_manager.local_processes(None).unwrap();
753        assert_eq!(processes.len(), 1);
754
755        let process = &processes[0];
756        assert_eq!(process.id, 42);
757        assert_eq!(&process.catalog, "test_catalog");
758        assert_eq!(process.schemas, vec!["schema1", "schema2"]);
759        assert_eq!(&process.query, "SELECT COUNT(*) FROM users WHERE age > 18");
760        assert_eq!(&process.client, "test_client");
761        assert_eq!(&process.frontend, "127.0.0.1:8000");
762        assert!(process.start_timestamp > 0);
763    }
764
765    #[tokio::test]
766    async fn test_ticket_drop_deregisters_process() {
767        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
768
769        {
770            let _ticket = process_manager.clone().register_query(
771                "public".to_string(),
772                vec!["test".to_string()],
773                "SELECT * FROM table".to_string(),
774                "client1".to_string(),
775                None,
776                None,
777            );
778
779            // Process should be registered
780            assert_eq!(process_manager.local_processes(None).unwrap().len(), 1);
781        } // ticket goes out of scope here
782
783        // Process should be automatically deregistered
784        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
785    }
786}