1use parking_lot::RwLock;
2use std::collections::HashMap;
3use std::sync::{Arc, Weak};
4use std::time::Instant;
5
6use linkrs_query::executor::streaming::runtime::ExecutionRuntime;
7use linkrs_query::executor::streaming::StreamingQueryResult;
8
9use super::cursor_context::{CursorContext, CursorPage};
10use super::query_context::QueryContext;
11use super::role_context::RoleContext;
12use super::session::Session;
13use super::space_context::SpaceContext;
14use super::statistics::StatisticsContext;
15use super::transaction_context::TransactionContext;
16use crate::api::session_variables::SessionVariables;
17use linkrs_core::error::QueryResult;
18use linkrs_core::types::SpaceSummary;
19use linkrs_core::Value;
20
21#[derive(Debug)]
22pub struct ClientSession {
23 session_context: super::session::SessionContext,
24 space_context: SpaceContext,
25 role_context: RoleContext,
26 query_context: QueryContext,
27 transaction_context: TransactionContext,
28 statistics_context: StatisticsContext,
29 cursor_context: CursorContext,
30 idle_start_time: Arc<RwLock<Instant>>,
31 session_variables: Arc<SessionVariables>,
33}
34
35impl ClientSession {
36 pub fn new(session: Session) -> Arc<Self> {
37 Arc::new(Self {
38 session_context: super::session::SessionContext::new(session),
39 space_context: SpaceContext::new(),
40 role_context: RoleContext::new(),
41 query_context: QueryContext::new(),
42 transaction_context: TransactionContext::new(),
43 statistics_context: StatisticsContext::new(),
44 cursor_context: CursorContext::new(),
45 idle_start_time: Arc::new(RwLock::new(Instant::now())),
46 session_variables: Arc::new(SessionVariables::new()),
47 })
48 }
49
50 pub fn id(&self) -> i64 {
51 self.session_context.id()
52 }
53
54 pub fn space(&self) -> Option<SpaceSummary> {
55 self.space_context.space()
56 }
57
58 pub fn set_space(&self, space: SpaceSummary) {
59 self.session_context.update_space_name(space.name.clone());
60 self.space_context.set_space(space);
61 }
62
63 pub fn clear_space(&self) {
64 self.space_context.clear_space();
65 }
66
67 pub fn space_name(&self) -> Option<String> {
68 self.session_context.space_name()
69 }
70
71 pub fn user(&self) -> String {
72 self.session_context.user()
73 }
74
75 pub fn roles(&self) -> std::collections::HashMap<i64, linkrs_core::RoleType> {
76 self.role_context.roles()
77 }
78
79 pub fn role_with_space(&self, space: i64) -> Option<linkrs_core::RoleType> {
80 self.role_context.role_with_space(space)
81 }
82
83 pub fn is_god(&self) -> bool {
84 self.role_context.is_god()
85 }
86
87 pub fn is_admin(&self) -> bool {
88 self.role_context.is_admin()
89 }
90
91 pub fn is_schema_admin(&self) -> bool {
92 self.role_context.is_schema_admin()
93 }
94
95 pub fn set_role(&self, space: i64, role: linkrs_core::RoleType) {
96 self.role_context.set_role(space, role);
97 }
98
99 pub fn idle_seconds(&self) -> u64 {
100 self.idle_start_time.read().elapsed().as_secs()
101 }
102
103 pub fn charge(&self) {
104 *self.idle_start_time.write() = Instant::now();
105 }
106
107 pub fn timezone(&self) -> Option<i32> {
108 self.session_context.timezone()
109 }
110
111 pub fn set_timezone(&self, timezone: i32) {
112 self.session_context.set_timezone(timezone);
113 }
114
115 pub fn graph_addr(&self) -> Option<String> {
116 self.session_context.graph_addr()
117 }
118
119 pub fn update_graph_addr(&self, host_addr: String) {
120 self.session_context.update_graph_addr(host_addr);
121 }
122
123 pub fn get_session(&self) -> Session {
124 self.session_context.get_session()
125 }
126
127 pub fn update_space_name(&self, space_name: String) {
128 self.session_context.update_space_name(space_name);
129 }
130
131 pub fn add_query(&self, ep_id: u32, query_context: String) {
132 self.query_context
133 .add_query(ep_id, query_context, self.id());
134 }
135
136 pub fn register_streaming_query(
138 &self,
139 query_id: u32,
140 query_text: String,
141 runtime: Weak<ExecutionRuntime>,
142 ) {
143 self.query_context
144 .register_streaming_query(query_id, query_text, runtime, self.id());
145 }
146
147 pub fn unregister_streaming_query(&self, query_id: u32) {
149 self.query_context
150 .unregister_streaming_query(query_id, self.id());
151 }
152
153 pub fn delete_query(&self, ep_id: u32) {
154 self.query_context.delete_query(ep_id, self.id());
155 }
156
157 pub fn find_query(&self, ep_id: u32) -> bool {
158 self.query_context.find_query(ep_id)
159 }
160
161 pub fn mark_query_killed(&self, ep_id: u32) {
162 self.query_context.mark_query_killed(ep_id, self.id());
163 }
164
165 pub fn mark_all_queries_killed(&self) {
166 self.query_context.mark_all_queries_killed(self.id());
167 }
168
169 pub fn active_queries_count(&self) -> usize {
170 self.query_context.active_queries_count()
171 }
172
173 pub fn kill_query(&self, query_id: u32) -> QueryResult<()> {
174 self.query_context.kill_query(query_id, self.id())
175 }
176
177 pub fn kill_multiple_queries(&self, query_ids: &[u32]) -> Vec<QueryResult<()>> {
178 self.query_context
179 .kill_multiple_queries(query_ids, self.id())
180 }
181
182 pub fn open_cursor(
184 &self,
185 query: String,
186 result: StreamingQueryResult,
187 columns: Vec<String>,
188 ) -> Result<u64, String> {
189 self.cursor_context.open(query, result, columns)
190 }
191
192 pub fn fetch_cursor_page(
194 &self,
195 cursor_id: u64,
196 page_size: usize,
197 ) -> Result<CursorPage, String> {
198 self.cursor_context.fetch(cursor_id, page_size)
199 }
200
201 pub fn close_cursor(&self, cursor_id: u64) -> bool {
203 self.cursor_context.close(cursor_id)
204 }
205
206 pub fn close_all_cursors(&self) {
208 self.cursor_context.close_all();
209 }
210
211 pub fn cursor_count(&self) -> usize {
213 self.cursor_context.active_count()
214 }
215
216 pub fn current_transaction(&self) -> Option<linkrs_transaction::TransactionId> {
217 self.transaction_context.current_transaction()
218 }
219
220 pub fn bind_transaction(&self, txn_id: linkrs_transaction::TransactionId) {
221 self.transaction_context.bind_transaction(txn_id, self.id());
222 }
223
224 pub fn unbind_transaction(&self) {
225 self.transaction_context.unbind_transaction(self.id());
226 }
227
228 pub fn has_active_transaction(&self) -> bool {
229 self.transaction_context.has_active_transaction()
230 }
231
232 pub fn is_auto_commit(&self) -> bool {
233 self.transaction_context.is_auto_commit()
234 }
235
236 pub fn set_auto_commit(&self, auto_commit: bool) {
237 self.transaction_context
238 .set_auto_commit(auto_commit, self.id());
239 }
240
241 pub fn transaction_options(&self) -> linkrs_transaction::TransactionOptions {
242 self.transaction_context.transaction_options()
243 }
244
245 pub fn set_transaction_options(&self, options: linkrs_transaction::TransactionOptions) {
246 self.transaction_context.set_transaction_options(options);
247 }
248
249 pub fn push_savepoint(&self, savepoint_id: linkrs_transaction::SavepointId) {
250 self.transaction_context
251 .push_savepoint(savepoint_id, self.id());
252 }
253
254 pub fn savepoint_stack(&self) -> Vec<linkrs_transaction::SavepointId> {
255 self.transaction_context.savepoint_stack()
256 }
257
258 pub fn clear_savepoints(&self) {
259 self.transaction_context.clear_savepoints(self.id());
260 }
261
262 pub fn savepoint_count(&self) -> usize {
263 self.transaction_context.savepoint_count()
264 }
265
266 pub fn statistics(&self) -> &linkrs_core::SessionStatistics {
267 self.statistics_context.statistics()
268 }
269
270 pub fn set_variable(&self, name: String, value: Value) {
275 self.session_variables
276 .set_variable(name, value, self.has_active_transaction());
277 }
278
279 pub fn variable_value(&self, name: &str) -> Option<Value> {
282 self.session_variables.variable_value(name)
283 }
284
285 pub fn variables_snapshot(&self) -> HashMap<String, Value> {
288 self.session_variables.variables_snapshot()
289 }
290
291 pub fn commit_variables(&self) {
294 self.session_variables.commit_variables();
295 }
296
297 pub fn rollback_variables(&self) {
299 self.session_variables.rollback_variables();
300 }
301
302 pub fn rollback_variables_to(&self, savepoint_name: &str) -> bool {
305 self.session_variables.rollback_variables_to(savepoint_name)
306 }
307
308 pub fn release_variable_savepoint(&self, savepoint_name: &str) {
311 self.session_variables
312 .release_variable_savepoint(savepoint_name);
313 }
314
315 pub fn push_variable_savepoint(&self, savepoint_name: &str) {
318 self.session_variables
319 .push_variable_savepoint(savepoint_name);
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use linkrs_core::types::DataType;
327
328 #[test]
329 fn test_client_session_creation() {
330 let session = Session {
331 session_id: 123,
332 user_name: "testuser".to_string(),
333 space_name: None,
334 graph_addr: None,
335 timezone: None,
336 };
337
338 let client_session = ClientSession::new(session);
339
340 assert_eq!(client_session.id(), 123);
341 assert_eq!(client_session.user(), "testuser");
342 assert_eq!(client_session.roles().len(), 0);
343 assert!(!client_session.is_admin());
344 }
345
346 #[test]
347 fn test_client_session_space_management() {
348 let session = Session {
349 session_id: 123,
350 user_name: "testuser".to_string(),
351 space_name: None,
352 graph_addr: None,
353 timezone: None,
354 };
355
356 let client_session = ClientSession::new(session);
357
358 assert!(client_session.space().is_none());
359 assert!(client_session.space_name().is_none());
360
361 let space_info = SpaceSummary::new(456, "test_space".to_string(), DataType::BigInt);
362 client_session.set_space(space_info.clone());
363
364 assert_eq!(client_session.space().expect("space should exist").id, 456);
365 assert_eq!(
366 client_session.space().expect("space should exist").name,
367 "test_space"
368 );
369 client_session.update_space_name("new_space".to_string());
370 assert_eq!(
371 client_session
372 .space_name()
373 .expect("space_name should exist"),
374 "new_space"
375 );
376
377 client_session.clear_space();
378 assert!(client_session.space().is_none());
379 }
380
381 #[test]
382 fn test_client_session_role_management() {
383 let session = Session {
384 session_id: 123,
385 user_name: "testuser".to_string(),
386 space_name: None,
387 graph_addr: None,
388 timezone: None,
389 };
390
391 let client_session = ClientSession::new(session);
392
393 assert!(client_session.role_with_space(1).is_none());
394 assert!(!client_session.is_admin());
395 assert!(!client_session.is_god());
396
397 client_session.set_role(1, linkrs_core::RoleType::Admin);
398 assert_eq!(
399 client_session
400 .role_with_space(1)
401 .expect("role should exist"),
402 linkrs_core::RoleType::Admin
403 );
404 assert!(client_session.is_admin());
405 assert!(!client_session.is_god());
406
407 client_session.set_role(2, linkrs_core::RoleType::God);
408 assert!(client_session.is_god());
409 }
410
411 #[test]
412 fn test_client_session_idle_time() {
413 let session = Session {
414 session_id: 123,
415 user_name: "testuser".to_string(),
416 space_name: None,
417 graph_addr: None,
418 timezone: None,
419 };
420
421 let client_session = ClientSession::new(session);
422
423 let idle1 = client_session.idle_seconds();
424 assert_eq!(idle1, 0);
425
426 std::thread::sleep(std::time::Duration::from_millis(1100));
427 let idle2 = client_session.idle_seconds();
428 assert!(idle2 > 0);
429
430 client_session.charge();
431 let idle3 = client_session.idle_seconds();
432 assert_eq!(idle3, 0);
433 }
434
435 #[test]
436 fn test_client_session_query_management() {
437 let session = Session {
438 session_id: 123,
439 user_name: "testuser".to_string(),
440 space_name: None,
441 graph_addr: None,
442 timezone: None,
443 };
444
445 let client_session = ClientSession::new(session);
446
447 assert_eq!(client_session.active_queries_count(), 0);
448 assert!(!client_session.find_query(1));
449
450 client_session.add_query(1, "SELECT * FROM user".to_string());
451 assert_eq!(client_session.active_queries_count(), 1);
452 assert!(client_session.find_query(1));
453
454 client_session.delete_query(1);
455 assert_eq!(client_session.active_queries_count(), 0);
456 assert!(!client_session.find_query(1));
457
458 client_session.add_query(2, "MATCH (n) RETURN n".to_string());
459 let result = client_session.kill_query(2);
460 assert!(result.is_ok());
461 assert!(!client_session.find_query(2));
462
463 let result = client_session.kill_query(999);
464 assert!(result.is_err());
465
466 client_session.add_query(3, "query 3".to_string());
467 client_session.add_query(4, "query 4".to_string());
468 let results = client_session.kill_multiple_queries(&[3, 4, 5]);
469 assert_eq!(results.len(), 3);
470 assert!(results[0].is_ok());
471 assert!(results[1].is_ok());
472 assert!(results[2].is_err());
473 }
474
475 #[test]
476 fn test_client_session_transaction_management() {
477 let session = Session {
478 session_id: 123,
479 user_name: "testuser".to_string(),
480 space_name: None,
481 graph_addr: None,
482 timezone: None,
483 };
484
485 let client_session = ClientSession::new(session);
486
487 assert!(client_session.current_transaction().is_none());
488 assert!(!client_session.has_active_transaction());
489 assert!(client_session.is_auto_commit());
490
491 client_session.bind_transaction(linkrs_transaction::TransactionId(1001));
492 assert_eq!(
493 client_session
494 .current_transaction()
495 .expect("current_transaction should exist"),
496 linkrs_transaction::TransactionId(1001)
497 );
498 assert!(client_session.has_active_transaction());
499
500 client_session.unbind_transaction();
501 assert!(client_session.current_transaction().is_none());
502
503 client_session.set_auto_commit(false);
504 assert!(!client_session.is_auto_commit());
505
506 let options = linkrs_transaction::TransactionOptions::default();
507 client_session.set_transaction_options(options.clone());
508 assert_eq!(client_session.transaction_options(), options);
509 }
510
511 #[test]
512 fn test_client_session_savepoint_management() {
513 let session = Session {
514 session_id: 123,
515 user_name: "testuser".to_string(),
516 space_name: None,
517 graph_addr: None,
518 timezone: None,
519 };
520
521 let client_session = ClientSession::new(session);
522
523 assert_eq!(client_session.savepoint_count(), 0);
524 assert!(client_session.savepoint_stack().is_empty());
525
526 client_session.push_savepoint(1);
527 client_session.push_savepoint(2);
528 assert_eq!(client_session.savepoint_count(), 2);
529 assert_eq!(client_session.savepoint_stack(), vec![1, 2]);
530
531 client_session.clear_savepoints();
532 assert_eq!(client_session.savepoint_count(), 0);
533 }
534
535 #[test]
536 fn test_session_variables_outside_transaction() {
537 let session = Session {
538 session_id: 123,
539 user_name: "testuser".to_string(),
540 space_name: None,
541 graph_addr: None,
542 timezone: None,
543 };
544
545 let client_session = ClientSession::new(session);
546
547 client_session.set_variable("x".into(), Value::Int(1));
548 assert_eq!(
549 client_session.variable_value("x"),
550 Some(Value::Int(1)),
551 "set outside a transaction writes the base store"
552 );
553
554 client_session.set_variable("x".into(), Value::Int(2));
556 assert_eq!(client_session.variable_value("x"), Some(Value::Int(2)));
557
558 let snapshot = client_session.variables_snapshot();
559 assert_eq!(snapshot.get("x"), Some(&Value::Int(2)));
560 }
561
562 #[test]
563 fn test_session_variables_transaction_rollback_restores() {
564 let session = Session {
565 session_id: 123,
566 user_name: "testuser".to_string(),
567 space_name: None,
568 graph_addr: None,
569 timezone: None,
570 };
571
572 let client_session = ClientSession::new(session);
573
574 client_session.set_variable("x".into(), Value::Int(1));
576
577 client_session.bind_transaction(linkrs_transaction::TransactionId(7));
578 assert!(client_session.has_active_transaction());
579
580 client_session.set_variable("x".into(), Value::Int(100));
582 client_session.set_variable("y".into(), Value::string("txn"));
583 assert_eq!(client_session.variable_value("x"), Some(Value::Int(100)));
584 assert_eq!(
585 client_session.variable_value("y"),
586 Some(Value::string("txn"))
587 );
588
589 client_session.rollback_variables();
591 client_session.unbind_transaction();
592 assert_eq!(client_session.variable_value("x"), Some(Value::Int(1)));
593 assert_eq!(client_session.variable_value("y"), None);
594 }
595
596 #[test]
597 fn test_session_variables_transaction_commit_merges() {
598 let session = Session {
599 session_id: 123,
600 user_name: "testuser".to_string(),
601 space_name: None,
602 graph_addr: None,
603 timezone: None,
604 };
605
606 let client_session = ClientSession::new(session);
607
608 client_session.bind_transaction(linkrs_transaction::TransactionId(8));
609 client_session.set_variable("a".into(), Value::Int(5));
610
611 client_session.commit_variables();
612 client_session.unbind_transaction();
613 assert_eq!(client_session.variable_value("a"), Some(Value::Int(5)));
614 }
615
616 #[test]
617 fn test_session_variables_rollback_to_savepoint() {
618 let session = Session {
619 session_id: 123,
620 user_name: "testuser".to_string(),
621 space_name: None,
622 graph_addr: None,
623 timezone: None,
624 };
625
626 let client_session = ClientSession::new(session);
627
628 client_session.bind_transaction(linkrs_transaction::TransactionId(9));
629
630 client_session.set_variable("a".into(), Value::Int(1));
631 client_session.push_variable_savepoint("sp1");
632 client_session.set_variable("a".into(), Value::Int(2));
633 client_session.set_variable("b".into(), Value::Int(3));
634
635 assert!(
636 client_session.rollback_variables_to("sp1"),
637 "savepoint marker must be found"
638 );
639 assert_eq!(
640 client_session.variable_value("a"),
641 Some(Value::Int(1)),
642 "assignment after the savepoint is restored"
643 );
644 assert_eq!(client_session.variable_value("b"), None);
645
646 client_session.set_variable("b".into(), Value::Int(4));
648 assert!(!client_session.rollback_variables_to("missing"));
649 assert_eq!(client_session.variable_value("b"), Some(Value::Int(4)));
650
651 client_session.push_variable_savepoint("sp2");
653 client_session.release_variable_savepoint("sp2");
654 assert!(!client_session.rollback_variables_to("sp2"));
655 }
656}