linkrs_server/graph_service/
streaming.rs1use std::collections::HashMap;
2use std::sync::atomic::Ordering;
3use std::sync::Arc;
4
5use crate::query::executor::streaming::StreamingQueryResult;
6use crate::session::ClientSession;
7use crate::storage::{
8 StorageClient, StorageOperationContextOps, StorageSchemaContextOps, StorageSyncContextOps,
9};
10
11use super::error::GraphServiceError;
12use super::GraphService;
13
14impl<
15 S: StorageClient
16 + StorageSchemaContextOps
17 + StorageSyncContextOps
18 + StorageOperationContextOps
19 + Clone
20 + 'static,
21 > GraphService<S>
22{
23 pub async fn execute_stream(
25 &self,
26 session_id: i64,
27 stmt: &str,
28 ) -> Result<StreamingQueryResult, GraphServiceError> {
29 let session = self
30 .session_manager
31 .find_session(session_id)
32 .ok_or_else(|| GraphServiceError::new(format!("Invalid session ID: {}", session_id)))?;
33 let snapshot = session.variables_snapshot();
34 self.build_streaming_result(&session, stmt, None, Some(snapshot))
35 .await
36 }
37
38 pub async fn execute_stream_with_params(
40 &self,
41 session_id: i64,
42 stmt: &str,
43 parameters: Option<HashMap<String, linkrs_core::Value>>,
44 session_variables: Option<HashMap<String, linkrs_core::Value>>,
45 ) -> Result<StreamingQueryResult, GraphServiceError> {
46 let session = self
47 .session_manager
48 .find_session(session_id)
49 .ok_or_else(|| GraphServiceError::new(format!("Invalid session ID: {}", session_id)))?;
50 self.build_streaming_result(&session, stmt, parameters, session_variables)
51 .await
52 }
53
54 pub(crate) async fn build_streaming_result(
57 &self,
58 session: &Arc<ClientSession>,
59 stmt: &str,
60 parameters: Option<HashMap<String, linkrs_core::Value>>,
61 session_variables: Option<HashMap<String, linkrs_core::Value>>,
62 ) -> Result<StreamingQueryResult, GraphServiceError> {
63 let session_id = session.id();
64 if Self::is_config_statement(stmt) {
65 return Err(GraphServiceError::new(
66 "UPDATE CONFIGS and SHOW CONFIGS are not supported on streaming or export endpoints; use the unary query endpoint instead",
67 ));
68 }
69 match Self::parse_command(stmt) {
70 Err(parse_error) => return Err(parse_error),
71 Ok(Some(_)) => {
72 return self
73 .execute_with_params(session_id, stmt, parameters, session_variables)
74 .await
75 .map(|result| StreamingQueryResult::from_execution_result(result.execution));
76 }
77 Ok(None) => {}
78 }
79
80 if Self::parse_stmt(stmt).is_some_and(|parsed| {
88 Self::is_use_statement(&parsed) || Self::is_session_inspection(&parsed)
89 }) {
90 return self
91 .execute_with_params(session_id, stmt, parameters, session_variables)
92 .await
93 .map(|result| StreamingQueryResult::from_execution_result(result.execution));
94 }
95
96 let permission_context = super::context::QueryExecutionContext {
101 stmt,
102 parsed_ast: None,
103 space_id: session.space().map(|s| s.id as i64).unwrap_or(0),
104 parameters: None,
105 session_variables: None,
106 };
107 self.check_query_permission(&session.user(), &permission_context)?;
108
109 let query_id = self.next_query_id.fetch_add(1, Ordering::Relaxed) as u32;
110 let _ = self.query_manager.register_query_with_id(
113 query_id as i64,
114 session.id(),
115 session.user(),
116 session.space().map(|space| space.name),
117 stmt.to_string(),
118 );
119 let query_request = linkrs_api::api_core::QueryRequest {
120 isolation_level: None,
121 space_id: session.space().map(|s| s.id),
122 space_name: session.space().map(|s| s.name),
123 auto_commit: session.is_auto_commit(),
124 transaction_id: session.current_transaction(),
125 parameters,
126 session_variables,
127 query_id: Some(query_id as u64),
128 parsed_statement: None,
129 consistency: Default::default(),
130 };
131
132 let mut query_api = self.query_api.write();
133 let result = if let Some(txn_id) = session.current_transaction() {
134 let manager = self
135 .transaction_manager
136 .as_ref()
137 .ok_or_else(|| GraphServiceError::new("Transaction manager is not configured"))?;
138 manager
139 .refresh_statement_snapshot(txn_id)
140 .map_err(|error| GraphServiceError::new(error.to_string()))?;
141 let execution = manager
142 .create_execution(txn_id, false)
143 .map_err(|error| GraphServiceError::new(error.to_string()))?;
144 query_api
145 .execute_stream_with_execution(stmt, query_request, &execution)
146 .map_err(|e| GraphServiceError::from_core_error_with_query(e, stmt))?
147 } else {
148 let execution_storage = self
149 .storage
150 .bind_auto_commit_context()
151 .map_err(|error| GraphServiceError::new(error.to_string()))?;
152 query_api
153 .execute_stream_with_operation_storage(stmt, query_request, execution_storage)
154 .map_err(|e| GraphServiceError::from_core_error_with_query(e, stmt))?
155 };
156
157 result.runtime().assign_query_id(query_id as u64);
158 result
159 .runtime()
160 .set_progress_identity(session_id, query_id as i64);
161 result
162 .runtime()
163 .set_progress_rows_interval(self.progress_rows_interval);
164 session.register_streaming_query(query_id, stmt.to_string(), result.runtime_downgrade());
165
166 let session_clone = session.clone();
167 let tracked_manager = Arc::clone(&self.query_manager);
168 let tracked_id = query_id as i64;
169 result.set_on_drop(Box::new(move || {
170 session_clone.unregister_streaming_query(query_id);
171 if tracked_manager.get_query(tracked_id).is_some_and(|info| {
172 info.status == crate::query::query_manager::QueryStatus::Running
173 }) {
174 let _ = tracked_manager.finish_query(tracked_id);
175 }
176 tracked_manager.cleanup_finished_queries(1000);
177 }));
178
179 Ok(result)
180 }
181}