1mod stream;
16
17use std::collections::HashMap;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::task::{Context, Poll};
21
22use api::v1::GreptimeRequest;
23use arrow_flight::flight_service_server::FlightService;
24use arrow_flight::{
25 Action, ActionType, Criteria, Empty, FlightData, FlightDescriptor, FlightInfo,
26 HandshakeRequest, HandshakeResponse, PollInfo, PutResult, SchemaResult, Ticket,
27};
28use async_trait::async_trait;
29use bytes::{self, Bytes};
30use common_error::ext::ErrorExt;
31use common_grpc::flight::do_put::{DoPutMetadata, DoPutResponse};
32use common_grpc::flight::{
33 FLOW_EXTENSIONS_METADATA_KEY, FlightDecoder, FlightMessage, SNAPSHOT_SEQS_METADATA_KEY,
34};
35use common_memory_manager::MemoryGuard;
36use common_query::{Output, OutputData};
37use common_recordbatch::DfRecordBatch;
38use common_telemetry::debug;
39use common_telemetry::tracing::info_span;
40use common_telemetry::tracing_context::{FutureExt, TracingContext};
41use datatypes::arrow::datatypes::SchemaRef;
42use futures::{Stream, future, ready};
43use futures_util::{StreamExt, TryStreamExt};
44use prost::Message;
45use query::metrics::terminal_recordbatch_metrics_from_plan_if_requested;
46use query::options::FlowQueryExtensions;
47use session::context::{Channel, QueryContextRef};
48use snafu::{IntoError, OptionExt, ResultExt, ensure};
49use table::table_name::TableName;
50use tokio::sync::mpsc;
51use tokio_stream::wrappers::ReceiverStream;
52use tonic::{Request, Response, Status, Streaming};
53
54use crate::error::{InvalidParameterSnafu, InvalidQuerySnafu, Result, ToJsonSnafu};
55pub use crate::grpc::flight::stream::{
56 FlightRecordBatchSource, FlightRecordBatchStream, FlightRecordBatchStreamInput,
57};
58use crate::grpc::greptime_handler::{
59 GreptimeRequestHandler, create_query_context, get_request_type,
60};
61use crate::grpc::{FlightCompression, TonicResult, context_auth};
62use crate::request_memory_limiter::ServerMemoryLimiter;
63use crate::request_memory_metrics::RequestMemoryMetrics;
64use crate::{error, hint_headers};
65
66pub type TonicStream<T> = Pin<Box<dyn Stream<Item = TonicResult<T>> + Send + 'static>>;
67
68#[async_trait]
70pub trait FlightCraft: Send + Sync + 'static {
71 async fn do_get(
72 &self,
73 request: Request<Ticket>,
74 ) -> TonicResult<Response<TonicStream<FlightData>>>;
75
76 async fn do_put(
77 &self,
78 request: Request<Streaming<FlightData>>,
79 ) -> TonicResult<Response<TonicStream<PutResult>>> {
80 let _ = request;
81 Err(Status::unimplemented("Not yet implemented"))
82 }
83}
84
85pub type FlightCraftRef = Arc<dyn FlightCraft>;
86
87pub struct FlightCraftWrapper<T: FlightCraft>(pub T);
88
89impl<T: FlightCraft> From<T> for FlightCraftWrapper<T> {
90 fn from(t: T) -> Self {
91 Self(t)
92 }
93}
94
95#[async_trait]
96impl FlightCraft for FlightCraftRef {
97 async fn do_get(
98 &self,
99 request: Request<Ticket>,
100 ) -> TonicResult<Response<TonicStream<FlightData>>> {
101 (**self).do_get(request).await
102 }
103
104 async fn do_put(
105 &self,
106 request: Request<Streaming<FlightData>>,
107 ) -> TonicResult<Response<TonicStream<PutResult>>> {
108 self.as_ref().do_put(request).await
109 }
110}
111
112#[async_trait]
113impl<T: FlightCraft> FlightService for FlightCraftWrapper<T> {
114 type HandshakeStream = TonicStream<HandshakeResponse>;
115
116 async fn handshake(
117 &self,
118 _: Request<Streaming<HandshakeRequest>>,
119 ) -> TonicResult<Response<Self::HandshakeStream>> {
120 Err(Status::unimplemented("Not yet implemented"))
121 }
122
123 type ListFlightsStream = TonicStream<FlightInfo>;
124
125 async fn list_flights(
126 &self,
127 _: Request<Criteria>,
128 ) -> TonicResult<Response<Self::ListFlightsStream>> {
129 Err(Status::unimplemented("Not yet implemented"))
130 }
131
132 async fn get_flight_info(
133 &self,
134 _: Request<FlightDescriptor>,
135 ) -> TonicResult<Response<FlightInfo>> {
136 Err(Status::unimplemented("Not yet implemented"))
137 }
138
139 async fn poll_flight_info(
140 &self,
141 _: Request<FlightDescriptor>,
142 ) -> TonicResult<Response<PollInfo>> {
143 Err(Status::unimplemented("Not yet implemented"))
144 }
145
146 async fn get_schema(
147 &self,
148 _: Request<FlightDescriptor>,
149 ) -> TonicResult<Response<SchemaResult>> {
150 Err(Status::unimplemented("Not yet implemented"))
151 }
152
153 type DoGetStream = TonicStream<FlightData>;
154
155 async fn do_get(&self, request: Request<Ticket>) -> TonicResult<Response<Self::DoGetStream>> {
156 self.0.do_get(request).await
157 }
158
159 type DoPutStream = TonicStream<PutResult>;
160
161 async fn do_put(
162 &self,
163 request: Request<Streaming<FlightData>>,
164 ) -> TonicResult<Response<Self::DoPutStream>> {
165 self.0.do_put(request).await
166 }
167
168 type DoExchangeStream = TonicStream<FlightData>;
169
170 async fn do_exchange(
171 &self,
172 _: Request<Streaming<FlightData>>,
173 ) -> TonicResult<Response<Self::DoExchangeStream>> {
174 Err(Status::unimplemented("Not yet implemented"))
175 }
176
177 type DoActionStream = TonicStream<arrow_flight::Result>;
178
179 async fn do_action(&self, _: Request<Action>) -> TonicResult<Response<Self::DoActionStream>> {
180 Err(Status::unimplemented("Not yet implemented"))
181 }
182
183 type ListActionsStream = TonicStream<ActionType>;
184
185 async fn list_actions(
186 &self,
187 _: Request<Empty>,
188 ) -> TonicResult<Response<Self::ListActionsStream>> {
189 Err(Status::unimplemented("Not yet implemented"))
190 }
191}
192
193#[async_trait]
194impl FlightCraft for GreptimeRequestHandler {
195 async fn do_get(
196 &self,
197 request: Request<Ticket>,
198 ) -> TonicResult<Response<TonicStream<FlightData>>> {
199 let mut hints = hint_headers::extract_hints(request.metadata());
200 hints.extend(extract_flow_extensions(request.metadata())?);
201 let snapshot_seqs = extract_snapshot_seqs(request.metadata())?;
202
203 let ticket = request.into_inner().ticket;
204 let request =
205 GreptimeRequest::decode(ticket.as_ref()).context(error::InvalidFlightTicketSnafu)?;
206 let query_ctx =
207 create_query_context(Channel::Grpc, request.header.as_ref(), hints, snapshot_seqs)?;
208 let flow_extensions = FlowQueryExtensions::parse_flow_extensions(&query_ctx.extensions())
212 .map_err(|e| Status::invalid_argument(e.output_msg()))?;
213 let should_emit_terminal_metrics = flow_extensions
214 .as_ref()
215 .is_some_and(|extensions| extensions.should_collect_region_watermark());
216
217 let span = info_span!(
219 "GreptimeRequestHandler::do_get",
220 protocol = "grpc",
221 request_type = get_request_type(&request)
222 );
223 let flight_compression = self.flight_compression;
224 async {
225 let query = request.request.context(InvalidQuerySnafu {
226 reason: "Expecting non-empty GreptimeRequest.",
227 })?;
228 self.authenticate_request_with_query_ctx(request.header.as_ref(), &query_ctx)
229 .await?;
230 let output = self.handle_request_with_query_ctx(query, query_ctx.clone());
231 let stream = to_flight_data_stream(
232 output,
233 TracingContext::from_current_span(),
234 flight_compression,
235 query_ctx,
236 should_emit_terminal_metrics,
237 );
238 Ok(Response::new(stream))
239 }
240 .trace(span)
241 .await
242 }
243
244 async fn do_put(
245 &self,
246 request: Request<Streaming<FlightData>>,
247 ) -> TonicResult<Response<TonicStream<PutResult>>> {
248 let (headers, extensions, stream) = request.into_parts();
249
250 let limiter = extensions.get::<ServerMemoryLimiter>().cloned();
251
252 let query_ctx = context_auth::create_query_context_from_grpc_metadata(&headers)?;
253 context_auth::check_auth(self.user_provider.clone(), &headers, query_ctx.clone()).await?;
254
255 const MAX_PENDING_RESPONSES: usize = 32;
256 let (tx, rx) = mpsc::channel::<TonicResult<DoPutResponse>>(MAX_PENDING_RESPONSES);
257
258 let stream = PutRecordBatchRequestStream::new(
259 stream,
260 query_ctx.current_catalog().to_string(),
261 query_ctx.current_schema(),
262 limiter,
263 )
264 .await?;
265 let _ = tx.send(Ok(DoPutResponse::new(0, 0, 0.0))).await;
267 self.put_record_batches(stream, tx, query_ctx).await;
268
269 let response = ReceiverStream::new(rx)
270 .and_then(|response| {
271 future::ready({
272 serde_json::to_vec(&response)
273 .context(ToJsonSnafu)
274 .map(|x| PutResult {
275 app_metadata: Bytes::from(x),
276 })
277 .map_err(Into::into)
278 })
279 })
280 .boxed();
281 Ok(Response::new(response))
282 }
283}
284
285pub struct PutRecordBatchRequest {
286 pub table_name: TableName,
287 pub request_id: i64,
288 pub timestamp_range: Option<(i64, i64)>,
289 pub record_batch: DfRecordBatch,
290 pub schema_bytes: Bytes,
291 pub flight_data: FlightData,
292 pub(crate) _guard: Option<MemoryGuard<RequestMemoryMetrics>>,
293}
294
295impl PutRecordBatchRequest {
296 fn try_new(
297 table_name: TableName,
298 record_batch: DfRecordBatch,
299 request_id: i64,
300 timestamp_range: Option<(i64, i64)>,
301 schema_bytes: Bytes,
302 flight_data: FlightData,
303 limiter: Option<&ServerMemoryLimiter>,
304 ) -> Result<Self> {
305 let memory_usage = flight_data.data_body.len()
306 + flight_data.app_metadata.len()
307 + flight_data.data_header.len();
308
309 let _guard = if let Some(limiter) = limiter {
310 let guard = limiter.try_acquire(memory_usage as u64).ok_or_else(|| {
311 let inner_err = common_memory_manager::Error::MemoryLimitExceeded {
312 requested_bytes: memory_usage as u64,
313 limit_bytes: limiter.limit_bytes(),
314 };
315 error::MemoryLimitExceededSnafu.into_error(inner_err)
316 })?;
317 Some(guard)
318 } else {
319 None
320 };
321
322 Ok(Self {
323 table_name,
324 request_id,
325 timestamp_range,
326 record_batch,
327 schema_bytes,
328 flight_data,
329 _guard,
330 })
331 }
332}
333
334pub struct PutRecordBatchRequestStream {
335 flight_data_stream: Streaming<FlightData>,
336 catalog: String,
337 schema_name: String,
338 limiter: Option<ServerMemoryLimiter>,
339 state: StreamState,
342}
343
344enum StreamState {
345 Init,
346 Ready {
347 table_name: TableName,
348 schema: SchemaRef,
349 schema_bytes: Bytes,
350 decoder: FlightDecoder,
351 },
352}
353
354impl PutRecordBatchRequestStream {
355 pub async fn new(
358 flight_data_stream: Streaming<FlightData>,
359 catalog: String,
360 schema: String,
361 limiter: Option<ServerMemoryLimiter>,
362 ) -> TonicResult<Self> {
363 Ok(Self {
364 flight_data_stream,
365 catalog,
366 schema_name: schema,
367 limiter,
368 state: StreamState::Init,
369 })
370 }
371
372 pub fn table_name(&self) -> Option<&TableName> {
375 match &self.state {
376 StreamState::Init => None,
377 StreamState::Ready { table_name, .. } => Some(table_name),
378 }
379 }
380
381 pub fn schema(&self) -> Option<&SchemaRef> {
384 match &self.state {
385 StreamState::Init => None,
386 StreamState::Ready { schema, .. } => Some(schema),
387 }
388 }
389
390 pub fn schema_bytes(&self) -> Option<&Bytes> {
393 match &self.state {
394 StreamState::Init => None,
395 StreamState::Ready { schema_bytes, .. } => Some(schema_bytes),
396 }
397 }
398
399 fn extract_table_name(mut descriptor: FlightDescriptor) -> Result<String> {
400 ensure!(
401 descriptor.r#type == arrow_flight::flight_descriptor::DescriptorType::Path as i32,
402 InvalidParameterSnafu {
403 reason: "expect FlightDescriptor::type == 'Path' only",
404 }
405 );
406 ensure!(
407 descriptor.path.len() == 1,
408 InvalidParameterSnafu {
409 reason: "expect FlightDescriptor::path has only one table name",
410 }
411 );
412 Ok(descriptor.path.remove(0))
413 }
414}
415
416impl Stream for PutRecordBatchRequestStream {
417 type Item = TonicResult<PutRecordBatchRequest>;
418
419 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
420 loop {
421 let poll = ready!(self.flight_data_stream.poll_next_unpin(cx));
422
423 match poll {
424 Some(Ok(flight_data)) => {
425 let limiter = self.limiter.clone();
426
427 match &mut self.state {
428 StreamState::Init => {
429 let flight_descriptor = match flight_data.flight_descriptor.as_ref() {
431 Some(descriptor) => descriptor.clone(),
432 None => {
433 return Poll::Ready(Some(Err(Status::failed_precondition(
434 "table to put is not found in flight descriptor",
435 ))));
436 }
437 };
438
439 let table_name_str = match Self::extract_table_name(flight_descriptor) {
440 Ok(name) => name,
441 Err(e) => {
442 return Poll::Ready(Some(Err(Status::invalid_argument(
443 e.to_string(),
444 ))));
445 }
446 };
447 let table_name = TableName::new(
448 self.catalog.clone(),
449 self.schema_name.clone(),
450 table_name_str,
451 );
452
453 let mut decoder = FlightDecoder::default();
455 let schema_message = decoder.try_decode(&flight_data).map_err(|e| {
456 Status::invalid_argument(format!("Failed to decode schema: {}", e))
457 })?;
458
459 match schema_message {
460 Some(FlightMessage::Schema(schema)) => {
461 let schema_bytes = decoder.schema_bytes().ok_or_else(|| {
462 Status::internal(
463 "decoder should have schema bytes after decoding schema",
464 )
465 })?;
466
467 self.state = StreamState::Ready {
469 table_name,
470 schema,
471 schema_bytes,
472 decoder,
473 };
474 continue;
476 }
477 _ => {
478 return Poll::Ready(Some(Err(Status::failed_precondition(
479 "first message must be a Schema message",
480 ))));
481 }
482 }
483 }
484 StreamState::Ready {
485 table_name,
486 schema: _,
487 schema_bytes,
488 decoder,
489 } => {
490 let metadata = if !flight_data.app_metadata.is_empty() {
492 serde_json::from_slice::<DoPutMetadata>(&flight_data.app_metadata)
493 .ok()
494 } else {
495 None
496 };
497 let request_id = metadata
498 .as_ref()
499 .map(|meta| meta.request_id())
500 .unwrap_or_default();
501 let timestamp_range = metadata.and_then(|meta| meta.timestamp_range());
502
503 match decoder.try_decode(&flight_data) {
505 Ok(Some(FlightMessage::RecordBatch(record_batch))) => {
506 let table_name = table_name.clone();
507 let schema_bytes = schema_bytes.clone();
508 return Poll::Ready(Some(
509 PutRecordBatchRequest::try_new(
510 table_name,
511 record_batch,
512 request_id,
513 timestamp_range,
514 schema_bytes,
515 flight_data,
516 limiter.as_ref(),
517 )
518 .map_err(|e| Status::invalid_argument(e.to_string())),
519 ));
520 }
521 Ok(Some(other)) => {
522 debug!("Unexpected flight message: {:?}", other);
523 return Poll::Ready(Some(Err(Status::invalid_argument(
524 "Expected RecordBatch message, got other message type",
525 ))));
526 }
527 Ok(None) => {
528 continue;
530 }
531 Err(e) => {
532 return Poll::Ready(Some(Err(Status::invalid_argument(
533 format!("Failed to decode RecordBatch: {}", e),
534 ))));
535 }
536 }
537 }
538 }
539 }
540 Some(Err(e)) => {
541 return Poll::Ready(Some(Err(e)));
542 }
543 None => {
544 return Poll::Ready(None);
545 }
546 }
547 }
548 }
549}
550
551fn extract_flow_extensions(
552 metadata: &tonic::metadata::MetadataMap,
553) -> TonicResult<Vec<(String, String)>> {
554 Ok(extract_json_metadata(metadata, FLOW_EXTENSIONS_METADATA_KEY)?.unwrap_or_default())
555}
556
557fn extract_snapshot_seqs(
558 metadata: &tonic::metadata::MetadataMap,
559) -> TonicResult<HashMap<u64, u64>> {
560 Ok(extract_json_metadata(metadata, SNAPSHOT_SEQS_METADATA_KEY)?.unwrap_or_default())
561}
562
563fn extract_json_metadata<T: serde::de::DeserializeOwned>(
564 metadata: &tonic::metadata::MetadataMap,
565 key: &'static str,
566) -> TonicResult<Option<T>> {
567 let Some(value) = metadata.get(key) else {
568 return Ok(None);
569 };
570
571 let value = value
572 .to_str()
573 .map_err(|e| Status::invalid_argument(format!("Invalid {key} metadata value: {e}")))?;
574
575 let parsed = serde_json::from_str::<T>(value)
576 .map_err(|e| Status::invalid_argument(format!("Invalid {key} metadata JSON: {e}")))?;
577 Ok(Some(parsed))
578}
579
580fn to_flight_data_stream<F>(
581 output: F,
582 tracing_context: TracingContext,
583 flight_compression: FlightCompression,
584 query_ctx: QueryContextRef,
585 should_emit_terminal_metrics: bool,
586) -> TonicStream<FlightData>
587where
588 F: std::future::Future<Output = Result<Output>> + Send + 'static,
589{
590 let initializer = async move {
591 let output = output.await.map_err(Status::from)?;
592 output_to_flight_record_batch_source(output, should_emit_terminal_metrics)
593 };
594 let stream = FlightRecordBatchStream::new(
595 FlightRecordBatchStreamInput::initializer(initializer),
596 tracing_context,
597 flight_compression,
598 query_ctx,
599 );
600 Box::pin(stream) as _
601}
602
603fn output_to_flight_record_batch_source(
604 output: Output,
605 should_emit_terminal_metrics: bool,
606) -> TonicResult<FlightRecordBatchSource> {
607 match output.data {
608 OutputData::Stream(stream) => Ok(FlightRecordBatchSource::RecordBatches(stream)),
609 OutputData::RecordBatches(x) => Ok(FlightRecordBatchSource::RecordBatches(x.as_stream())),
610 OutputData::AffectedRows(rows) => {
611 let terminal_metrics = match terminal_recordbatch_metrics_from_plan_if_requested(
612 output.meta.plan,
613 should_emit_terminal_metrics,
614 ) {
615 Some(metrics) => match serde_json::to_string(&metrics) {
616 Ok(metrics) => Some(metrics),
617 Err(e) => {
618 return Err(Status::internal(format!(
619 "Failed to serialize terminal metrics: {e}"
620 )));
621 }
622 },
623 None => None,
624 };
625 Ok(FlightRecordBatchSource::AffectedRows {
626 rows,
627 metrics: terminal_metrics,
628 })
629 }
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use query::options::FLOW_SCHEDULED_TIME_MILLIS;
636 use tonic::metadata::{AsciiMetadataValue, MetadataMap};
637
638 use super::*;
639
640 #[test]
641 fn test_extract_flow_extensions_preserves_comma_bearing_values() {
642 let mut metadata = MetadataMap::new();
643 metadata.insert(
644 FLOW_EXTENSIONS_METADATA_KEY,
645 AsciiMetadataValue::try_from(
646 r#"[["flow.return_region_seq","true"],["flow.incremental_after_seqs","{\"1\":10,\"2\":20}"]]"#,
647 )
648 .unwrap(),
649 );
650
651 let extensions = extract_flow_extensions(&metadata).unwrap();
652 assert_eq!(
653 extensions,
654 vec![
655 ("flow.return_region_seq".to_string(), "true".to_string()),
656 (
657 "flow.incremental_after_seqs".to_string(),
658 r#"{"1":10,"2":20}"#.to_string()
659 ),
660 ]
661 );
662 }
663
664 #[test]
665 fn test_flow_extensions_can_carry_scheduled_time() {
666 let mut metadata = MetadataMap::new();
667 metadata.insert(
668 FLOW_EXTENSIONS_METADATA_KEY,
669 AsciiMetadataValue::try_from(r#"[["flow.scheduled_time_millis","1700000000000"]]"#)
670 .unwrap(),
671 );
672
673 let flow_extensions = extract_flow_extensions(&metadata).unwrap();
674 let query_ctx =
675 create_query_context(Channel::Grpc, None, flow_extensions, HashMap::new()).unwrap();
676
677 assert_eq!(
678 query_ctx.extension(FLOW_SCHEDULED_TIME_MILLIS),
679 Some("1700000000000")
680 );
681 }
682
683 #[test]
684 fn test_extract_flow_extensions_rejects_invalid_json() {
685 let mut metadata = MetadataMap::new();
686 metadata.insert(
687 FLOW_EXTENSIONS_METADATA_KEY,
688 AsciiMetadataValue::try_from("not-json").unwrap(),
689 );
690
691 let err = extract_flow_extensions(&metadata).unwrap_err();
692 assert_eq!(err.code(), tonic::Code::InvalidArgument);
693 }
694}