1use crate::error::{DistributedError, Result};
7use arrow::record_batch::RecordBatch;
8use arrow_flight::{
9 Action, ActionType, Criteria, Empty, FlightData, FlightDescriptor, FlightEndpoint, FlightInfo,
10 HandshakeRequest, HandshakeResponse, PutResult, SchemaAsIpc, SchemaResult, Ticket,
11 flight_service_server::{FlightService, FlightServiceServer},
12};
13use arrow_ipc::writer::IpcWriteOptions;
14use bytes::Bytes;
15use futures::{Stream, StreamExt, stream};
16use std::collections::HashMap;
17use std::pin::Pin;
18use std::sync::{Arc, RwLock};
19use tonic::{Request, Response, Streaming};
20use tracing::{debug, info};
21
22pub struct FlightServer {
24 data_store: Arc<RwLock<HashMap<String, Arc<RecordBatch>>>>,
26 auth_tokens: Arc<RwLock<HashMap<String, String>>>,
28 enable_auth: bool,
30}
31
32impl FlightServer {
33 pub fn new() -> Self {
35 Self {
36 data_store: Arc::new(RwLock::new(HashMap::new())),
37 auth_tokens: Arc::new(RwLock::new(HashMap::new())),
38 enable_auth: false,
39 }
40 }
41
42 pub fn with_auth(mut self) -> Self {
44 self.enable_auth = true;
45 self
46 }
47
48 pub fn store_data(&self, ticket: String, data: Arc<RecordBatch>) -> Result<()> {
50 let mut store = self
51 .data_store
52 .write()
53 .map_err(|_| DistributedError::flight_rpc("Failed to acquire data store lock"))?;
54
55 store.insert(ticket, data);
56 Ok(())
57 }
58
59 pub fn get_data(&self, ticket: &str) -> Result<Option<Arc<RecordBatch>>> {
61 let store = self
62 .data_store
63 .read()
64 .map_err(|_| DistributedError::flight_rpc("Failed to acquire data store lock"))?;
65
66 Ok(store.get(ticket).cloned())
67 }
68
69 pub fn remove_data(&self, ticket: &str) -> Result<Option<Arc<RecordBatch>>> {
71 let mut store = self
72 .data_store
73 .write()
74 .map_err(|_| DistributedError::flight_rpc("Failed to acquire data store lock"))?;
75
76 Ok(store.remove(ticket))
77 }
78
79 pub fn list_tickets(&self) -> Result<Vec<String>> {
81 let store = self
82 .data_store
83 .read()
84 .map_err(|_| DistributedError::flight_rpc("Failed to acquire data store lock"))?;
85
86 Ok(store.keys().cloned().collect())
87 }
88
89 pub fn add_auth_token(&self, token: String, user: String) -> Result<()> {
91 let mut tokens = self
92 .auth_tokens
93 .write()
94 .map_err(|_| DistributedError::authentication("Failed to acquire auth tokens lock"))?;
95
96 tokens.insert(token, user);
97 Ok(())
98 }
99
100 pub fn into_service(self) -> FlightServiceServer<Self> {
102 FlightServiceServer::new(self)
103 }
104}
105
106impl Default for FlightServer {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112#[tonic::async_trait]
113impl FlightService for FlightServer {
114 type HandshakeStream =
115 Pin<Box<dyn Stream<Item = std::result::Result<HandshakeResponse, tonic::Status>> + Send>>;
116 type ListFlightsStream =
117 Pin<Box<dyn Stream<Item = std::result::Result<FlightInfo, tonic::Status>> + Send>>;
118 type DoGetStream =
119 Pin<Box<dyn Stream<Item = std::result::Result<FlightData, tonic::Status>> + Send>>;
120 type DoPutStream =
121 Pin<Box<dyn Stream<Item = std::result::Result<PutResult, tonic::Status>> + Send>>;
122 type DoActionStream = Pin<
123 Box<dyn Stream<Item = std::result::Result<arrow_flight::Result, tonic::Status>> + Send>,
124 >;
125 type ListActionsStream =
126 Pin<Box<dyn Stream<Item = std::result::Result<ActionType, tonic::Status>> + Send>>;
127 type DoExchangeStream =
128 Pin<Box<dyn Stream<Item = std::result::Result<FlightData, tonic::Status>> + Send>>;
129
130 async fn handshake(
131 &self,
132 _request: Request<Streaming<HandshakeRequest>>,
133 ) -> std::result::Result<Response<Self::HandshakeStream>, tonic::Status> {
134 debug!("Handshake request received");
135
136 let response = HandshakeResponse {
138 protocol_version: 0,
139 payload: Bytes::new(),
140 };
141
142 let stream = stream::once(async { Ok(response) });
143 Ok(Response::new(Box::pin(stream)))
144 }
145
146 async fn list_flights(
147 &self,
148 _request: Request<Criteria>,
149 ) -> std::result::Result<Response<Self::ListFlightsStream>, tonic::Status> {
150 debug!("List flights request received");
151
152 let stream = stream::empty();
154 Ok(Response::new(Box::pin(stream)))
155 }
156
157 async fn get_flight_info(
158 &self,
159 request: Request<FlightDescriptor>,
160 ) -> std::result::Result<Response<FlightInfo>, tonic::Status> {
161 let descriptor = request.into_inner();
162 debug!("Get flight info request: {:?}", descriptor);
163
164 let ticket_key = if !descriptor.path.is_empty() {
166 descriptor.path[0].clone()
167 } else if !descriptor.cmd.is_empty() {
168 String::from_utf8(descriptor.cmd.to_vec())
169 .map_err(|e| tonic::Status::invalid_argument(format!("Invalid cmd: {}", e)))?
170 } else {
171 return Err(tonic::Status::invalid_argument(
172 "FlightDescriptor must have a path or cmd",
173 ));
174 };
175
176 let data = self
177 .get_data(&ticket_key)
178 .map_err(|e| tonic::Status::internal(e.to_string()))?
179 .ok_or_else(|| tonic::Status::not_found(format!("Flight not found: {}", ticket_key)))?;
180
181 let schema = data.schema();
182 let ipc_opts = IpcWriteOptions::default();
183 let schema_bytes: arrow_flight::IpcMessage = SchemaAsIpc::new(schema.as_ref(), &ipc_opts)
184 .try_into()
185 .map_err(|e: arrow_schema::ArrowError| {
186 tonic::Status::internal(format!("Schema encode error: {}", e))
187 })?;
188
189 let endpoint = FlightEndpoint {
190 ticket: Some(Ticket {
191 ticket: Bytes::from(ticket_key),
192 }),
193 location: vec![],
194 expiration_time: None,
195 app_metadata: Bytes::new(),
196 };
197
198 let flight_info = FlightInfo {
199 schema: schema_bytes.0,
200 flight_descriptor: Some(descriptor),
201 endpoint: vec![endpoint],
202 total_records: data.num_rows() as i64,
203 total_bytes: -1,
204 ordered: false,
205 app_metadata: Bytes::new(),
206 };
207
208 Ok(Response::new(flight_info))
209 }
210
211 async fn get_schema(
212 &self,
213 request: Request<FlightDescriptor>,
214 ) -> std::result::Result<Response<SchemaResult>, tonic::Status> {
215 let descriptor = request.into_inner();
216 debug!("Get schema request received");
217
218 let ticket_key = if !descriptor.path.is_empty() {
219 descriptor.path[0].clone()
220 } else if !descriptor.cmd.is_empty() {
221 String::from_utf8(descriptor.cmd.to_vec())
222 .map_err(|e| tonic::Status::invalid_argument(format!("Invalid cmd: {}", e)))?
223 } else {
224 return Err(tonic::Status::invalid_argument(
225 "FlightDescriptor must have a path or cmd",
226 ));
227 };
228
229 let data = self
230 .get_data(&ticket_key)
231 .map_err(|e| tonic::Status::internal(e.to_string()))?
232 .ok_or_else(|| tonic::Status::not_found(format!("Flight not found: {}", ticket_key)))?;
233
234 let schema = data.schema();
235 let ipc_opts = IpcWriteOptions::default();
236 let schema_result: SchemaResult = SchemaAsIpc::new(schema.as_ref(), &ipc_opts)
237 .try_into()
238 .map_err(|e: arrow_schema::ArrowError| {
239 tonic::Status::internal(format!("Schema encode error: {}", e))
240 })?;
241
242 Ok(Response::new(schema_result))
243 }
244
245 async fn do_get(
246 &self,
247 request: Request<Ticket>,
248 ) -> std::result::Result<Response<Self::DoGetStream>, tonic::Status> {
249 let ticket = request.into_inner();
250 let ticket_str = String::from_utf8(ticket.ticket.to_vec())
251 .map_err(|e| tonic::Status::invalid_argument(format!("Invalid ticket: {}", e)))?;
252
253 info!("DoGet request for ticket: {}", ticket_str);
254
255 let data = self
257 .get_data(&ticket_str)
258 .map_err(|e| tonic::Status::internal(e.to_string()))?
259 .ok_or_else(|| tonic::Status::not_found(format!("Ticket not found: {}", ticket_str)))?;
260
261 let flight_data_vec = arrow_flight::utils::batches_to_flight_data(
263 data.schema().as_ref(),
264 vec![(*data).clone()],
265 )
266 .map_err(|e| tonic::Status::internal(format!("Failed to encode batches: {}", e)))?
267 .into_iter()
268 .map(Ok)
269 .collect::<Vec<_>>();
270
271 let stream = stream::iter(flight_data_vec);
272 Ok(Response::new(Box::pin(stream)))
273 }
274
275 async fn do_put(
276 &self,
277 request: Request<Streaming<FlightData>>,
278 ) -> std::result::Result<Response<Self::DoPutStream>, tonic::Status> {
279 debug!("DoPut request received");
280
281 let mut stream = request.into_inner();
282 let mut flight_data_vec = Vec::new();
283
284 while let Some(data_result) = stream.next().await {
286 flight_data_vec.push(data_result?);
287 }
288
289 let batches = arrow_flight::utils::flight_data_to_batches(&flight_data_vec)
291 .map_err(|e| tonic::Status::internal(format!("Failed to decode batches: {}", e)))?;
292
293 info!("DoPut received {} batches", batches.len());
294
295 for (i, batch) in batches.into_iter().enumerate() {
297 let ticket = format!("uploaded_{}", i);
298 self.store_data(ticket, Arc::new(batch))
299 .map_err(|e| tonic::Status::internal(e.to_string()))?;
300 }
301
302 let result = PutResult {
304 app_metadata: Bytes::new(),
305 };
306
307 let stream = stream::once(async { Ok(result) });
308 Ok(Response::new(Box::pin(stream)))
309 }
310
311 async fn do_action(
312 &self,
313 request: Request<Action>,
314 ) -> std::result::Result<Response<Self::DoActionStream>, tonic::Status> {
315 let action = request.into_inner();
316 info!("DoAction request: {}", action.r#type);
317
318 match action.r#type.as_str() {
319 "list_tickets" => {
320 let tickets = self
321 .list_tickets()
322 .map_err(|e| tonic::Status::internal(e.to_string()))?;
323
324 let result = arrow_flight::Result {
325 body: serde_json::to_vec(&tickets)
326 .map_err(|e| {
327 tonic::Status::internal(format!("Serialization error: {}", e))
328 })?
329 .into(),
330 };
331
332 let stream = stream::once(async { Ok(result) });
333 Ok(Response::new(Box::pin(stream)))
334 }
335 "remove_ticket" => {
336 let ticket = String::from_utf8(action.body.to_vec()).map_err(|e| {
337 tonic::Status::invalid_argument(format!("Invalid ticket: {}", e))
338 })?;
339
340 self.remove_data(&ticket)
341 .map_err(|e| tonic::Status::internal(e.to_string()))?;
342
343 let result = arrow_flight::Result {
344 body: Bytes::from("removed"),
345 };
346
347 let stream = stream::once(async { Ok(result) });
348 Ok(Response::new(Box::pin(stream)))
349 }
350 "list_actions" => {
351 let actions = vec![
353 serde_json::json!({"type": "list_tickets", "description": "List all available tickets"}),
354 serde_json::json!({"type": "remove_ticket", "description": "Remove a ticket from the server"}),
355 serde_json::json!({"type": "list_actions", "description": "List all supported actions (reflection)"}),
356 serde_json::json!({"type": "ping", "description": "Health check — returns 'pong'"}),
357 ];
358
359 let result = arrow_flight::Result {
360 body: serde_json::to_vec(&actions)
361 .map_err(|e| {
362 tonic::Status::internal(format!("Serialization error: {}", e))
363 })?
364 .into(),
365 };
366
367 let stream = stream::once(async { Ok(result) });
368 Ok(Response::new(Box::pin(stream)))
369 }
370 "ping" => {
371 let result = arrow_flight::Result {
372 body: Bytes::from_static(b"pong"),
373 };
374 let stream = stream::once(async { Ok(result) });
375 Ok(Response::new(Box::pin(stream)))
376 }
377 _ => Err(tonic::Status::unimplemented(format!(
378 "Action not implemented: {}",
379 action.r#type
380 ))),
381 }
382 }
383
384 async fn list_actions(
385 &self,
386 _request: Request<Empty>,
387 ) -> std::result::Result<Response<Self::ListActionsStream>, tonic::Status> {
388 debug!("List actions request received");
389
390 let actions = vec![
391 ActionType {
392 r#type: "list_tickets".to_string(),
393 description: "List all available tickets".to_string(),
394 },
395 ActionType {
396 r#type: "remove_ticket".to_string(),
397 description: "Remove a ticket from the server".to_string(),
398 },
399 ];
400
401 let stream = stream::iter(actions.into_iter().map(Ok));
402 Ok(Response::new(Box::pin(stream)))
403 }
404
405 async fn do_exchange(
406 &self,
407 request: Request<Streaming<FlightData>>,
408 ) -> std::result::Result<Response<Self::DoExchangeStream>, tonic::Status> {
409 debug!("DoExchange request received — echo/passthrough mode");
410
411 let mut incoming = request.into_inner();
412 let mut echo_items: Vec<std::result::Result<FlightData, tonic::Status>> = Vec::new();
413
414 while let Some(item) = incoming.next().await {
415 match item {
416 Ok(flight_data) => {
417 echo_items.push(Ok(flight_data));
418 }
419 Err(status) => {
420 echo_items.push(Err(status));
422 break;
423 }
424 }
425 }
426
427 info!("DoExchange echoing {} items", echo_items.len());
428 let stream = stream::iter(echo_items);
429 Ok(Response::new(Box::pin(stream)))
430 }
431
432 async fn poll_flight_info(
433 &self,
434 request: Request<FlightDescriptor>,
435 ) -> std::result::Result<Response<arrow_flight::PollInfo>, tonic::Status> {
436 let descriptor = request.into_inner();
437 debug!("Poll flight info request received");
438
439 let ticket_key = if !descriptor.path.is_empty() {
441 descriptor.path[0].clone()
442 } else if !descriptor.cmd.is_empty() {
443 String::from_utf8(descriptor.cmd.to_vec())
444 .map_err(|e| tonic::Status::invalid_argument(format!("Invalid cmd: {}", e)))?
445 } else {
446 return Err(tonic::Status::invalid_argument(
447 "FlightDescriptor must have a path or cmd",
448 ));
449 };
450
451 let data_opt = self
452 .get_data(&ticket_key)
453 .map_err(|e| tonic::Status::internal(e.to_string()))?;
454
455 if let Some(data) = data_opt {
458 let schema = data.schema();
459 let ipc_opts = IpcWriteOptions::default();
460 let schema_bytes: arrow_flight::IpcMessage =
461 SchemaAsIpc::new(schema.as_ref(), &ipc_opts)
462 .try_into()
463 .map_err(|e: arrow_schema::ArrowError| {
464 tonic::Status::internal(format!("Schema encode error: {}", e))
465 })?;
466
467 let endpoint = FlightEndpoint {
468 ticket: Some(Ticket {
469 ticket: Bytes::from(ticket_key),
470 }),
471 location: vec![],
472 expiration_time: None,
473 app_metadata: Bytes::new(),
474 };
475
476 let flight_info = FlightInfo {
477 schema: schema_bytes.0,
478 flight_descriptor: Some(descriptor),
479 endpoint: vec![endpoint],
480 total_records: data.num_rows() as i64,
481 total_bytes: -1,
482 ordered: false,
483 app_metadata: Bytes::new(),
484 };
485
486 Ok(Response::new(arrow_flight::PollInfo {
487 info: Some(flight_info),
488 flight_descriptor: None,
490 progress: Some(1.0),
491 expiration_time: None,
492 }))
493 } else {
494 Ok(Response::new(arrow_flight::PollInfo {
496 info: None,
497 flight_descriptor: Some(descriptor),
498 progress: None,
499 expiration_time: None,
500 }))
501 }
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508 use arrow::array::Int32Array;
509 use arrow::datatypes::{DataType, Field, Schema};
510
511 fn create_test_batch() -> std::result::Result<Arc<RecordBatch>, Box<dyn std::error::Error>> {
512 let schema = Arc::new(Schema::new(vec![Field::new(
513 "value",
514 DataType::Int32,
515 false,
516 )]));
517
518 let array = Int32Array::from(vec![1, 2, 3, 4, 5]);
519
520 Ok(Arc::new(RecordBatch::try_new(
521 schema,
522 vec![Arc::new(array)],
523 )?))
524 }
525
526 #[test]
527 fn test_server_creation() {
528 let server = FlightServer::new();
529 assert!(!server.enable_auth);
530 }
531
532 #[test]
533 fn test_store_and_retrieve_data() -> std::result::Result<(), Box<dyn std::error::Error>> {
534 let server = FlightServer::new();
535 let batch = create_test_batch()?;
536
537 server.store_data("test_ticket".to_string(), batch.clone())?;
538
539 let retrieved = server
540 .get_data("test_ticket")?
541 .ok_or_else(|| Box::<dyn std::error::Error>::from("should exist"))?;
542
543 assert_eq!(retrieved.num_rows(), batch.num_rows());
544 Ok(())
545 }
546
547 #[test]
548 fn test_remove_data() -> std::result::Result<(), Box<dyn std::error::Error>> {
549 let server = FlightServer::new();
550 let batch = create_test_batch()?;
551
552 server.store_data("test_ticket".to_string(), batch)?;
553
554 let removed = server
555 .remove_data("test_ticket")?
556 .ok_or_else(|| Box::<dyn std::error::Error>::from("should exist"))?;
557
558 assert_eq!(removed.num_rows(), 5);
559
560 let retrieved = server.get_data("test_ticket")?;
561 assert!(retrieved.is_none());
562 Ok(())
563 }
564
565 #[test]
566 fn test_list_tickets() -> std::result::Result<(), Box<dyn std::error::Error>> {
567 let server = FlightServer::new();
568
569 server.store_data("ticket1".to_string(), create_test_batch()?)?;
570 server.store_data("ticket2".to_string(), create_test_batch()?)?;
571
572 let tickets = server.list_tickets()?;
573 assert_eq!(tickets.len(), 2);
574 assert!(tickets.contains(&"ticket1".to_string()));
575 assert!(tickets.contains(&"ticket2".to_string()));
576 Ok(())
577 }
578
579 #[test]
580 fn test_authentication() -> std::result::Result<(), Box<dyn std::error::Error>> {
581 let server = FlightServer::new().with_auth();
582 assert!(server.enable_auth);
583
584 server.add_auth_token("token123".to_string(), "user1".to_string())?;
585
586 assert!(
588 server
589 .auth_tokens
590 .read()
591 .map_err(|e| Box::<dyn std::error::Error>::from(format!("lock poisoned: {}", e)))?
592 .contains_key("token123")
593 );
594 assert!(
595 !server
596 .auth_tokens
597 .read()
598 .map_err(|e| Box::<dyn std::error::Error>::from(format!("lock poisoned: {}", e)))?
599 .contains_key("invalid")
600 );
601 Ok(())
602 }
603}