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 let channel = request
203 .extensions()
204 .get::<Channel>()
205 .copied()
206 .unwrap_or(Channel::Grpc);
207
208 let ticket = request.into_inner().ticket;
209 let request =
210 GreptimeRequest::decode(ticket.as_ref()).context(error::InvalidFlightTicketSnafu)?;
211 let query_ctx =
212 create_query_context(channel, request.header.as_ref(), hints, snapshot_seqs)?;
213 let flow_extensions = FlowQueryExtensions::parse_flow_extensions(&query_ctx.extensions())
217 .map_err(|e| Status::invalid_argument(e.output_msg()))?;
218 let should_emit_terminal_metrics = flow_extensions
219 .as_ref()
220 .is_some_and(|extensions| extensions.should_collect_region_watermark());
221
222 let span = info_span!(
224 "GreptimeRequestHandler::do_get",
225 protocol = "grpc",
226 request_type = get_request_type(&request)
227 );
228 let flight_compression = self.flight_compression;
229 async {
230 let query = request.request.context(InvalidQuerySnafu {
231 reason: "Expecting non-empty GreptimeRequest.",
232 })?;
233 self.authenticate_request_with_query_ctx(request.header.as_ref(), &query_ctx)
234 .await?;
235 let output = self.handle_request_with_query_ctx(query, query_ctx.clone());
236 let stream = to_flight_data_stream(
237 output,
238 TracingContext::from_current_span(),
239 flight_compression,
240 query_ctx,
241 should_emit_terminal_metrics,
242 );
243 Ok(Response::new(stream))
244 }
245 .trace(span)
246 .await
247 }
248
249 async fn do_put(
250 &self,
251 request: Request<Streaming<FlightData>>,
252 ) -> TonicResult<Response<TonicStream<PutResult>>> {
253 let (headers, extensions, stream) = request.into_parts();
254
255 let limiter = extensions.get::<ServerMemoryLimiter>().cloned();
256
257 let query_ctx =
258 context_auth::create_query_context_from_grpc_metadata(&headers, &extensions)?;
259 context_auth::check_auth(self.user_provider.clone(), &headers, query_ctx.clone()).await?;
260
261 const MAX_PENDING_RESPONSES: usize = 32;
262 let (tx, rx) = mpsc::channel::<TonicResult<DoPutResponse>>(MAX_PENDING_RESPONSES);
263
264 let stream = PutRecordBatchRequestStream::new(
265 stream,
266 query_ctx.current_catalog().to_string(),
267 query_ctx.current_schema(),
268 limiter,
269 )
270 .await?;
271 let _ = tx.send(Ok(DoPutResponse::new(0, 0, 0.0))).await;
273 self.put_record_batches(stream, tx, query_ctx).await;
274
275 let response = ReceiverStream::new(rx)
276 .and_then(|response| {
277 future::ready({
278 serde_json::to_vec(&response)
279 .context(ToJsonSnafu)
280 .map(|x| PutResult {
281 app_metadata: Bytes::from(x),
282 })
283 .map_err(Into::into)
284 })
285 })
286 .boxed();
287 Ok(Response::new(response))
288 }
289}
290
291pub struct PutRecordBatchRequest {
292 pub table_name: TableName,
293 pub request_id: i64,
294 pub timestamp_range: Option<(i64, i64)>,
295 pub record_batch: DfRecordBatch,
296 pub schema_bytes: Bytes,
297 pub flight_data: FlightData,
298 pub(crate) _guard: Option<MemoryGuard<RequestMemoryMetrics>>,
299}
300
301impl PutRecordBatchRequest {
302 fn try_new(
303 table_name: TableName,
304 record_batch: DfRecordBatch,
305 request_id: i64,
306 timestamp_range: Option<(i64, i64)>,
307 schema_bytes: Bytes,
308 flight_data: FlightData,
309 limiter: Option<&ServerMemoryLimiter>,
310 ) -> Result<Self> {
311 let memory_usage = flight_data.data_body.len()
312 + flight_data.app_metadata.len()
313 + flight_data.data_header.len();
314
315 let _guard = if let Some(limiter) = limiter {
316 let guard = limiter.try_acquire(memory_usage as u64).ok_or_else(|| {
317 let inner_err = common_memory_manager::Error::MemoryLimitExceeded {
318 requested_bytes: memory_usage as u64,
319 limit_bytes: limiter.limit_bytes(),
320 };
321 error::MemoryLimitExceededSnafu.into_error(inner_err)
322 })?;
323 Some(guard)
324 } else {
325 None
326 };
327
328 Ok(Self {
329 table_name,
330 request_id,
331 timestamp_range,
332 record_batch,
333 schema_bytes,
334 flight_data,
335 _guard,
336 })
337 }
338}
339
340pub struct PutRecordBatchRequestStream {
341 flight_data_stream: Streaming<FlightData>,
342 catalog: String,
343 schema_name: String,
344 limiter: Option<ServerMemoryLimiter>,
345 state: StreamState,
348}
349
350enum StreamState {
351 Init,
352 Ready {
353 table_name: TableName,
354 schema: SchemaRef,
355 schema_bytes: Bytes,
356 decoder: FlightDecoder,
357 },
358}
359
360impl PutRecordBatchRequestStream {
361 pub async fn new(
364 flight_data_stream: Streaming<FlightData>,
365 catalog: String,
366 schema: String,
367 limiter: Option<ServerMemoryLimiter>,
368 ) -> TonicResult<Self> {
369 Ok(Self {
370 flight_data_stream,
371 catalog,
372 schema_name: schema,
373 limiter,
374 state: StreamState::Init,
375 })
376 }
377
378 pub fn table_name(&self) -> Option<&TableName> {
381 match &self.state {
382 StreamState::Init => None,
383 StreamState::Ready { table_name, .. } => Some(table_name),
384 }
385 }
386
387 pub fn schema(&self) -> Option<&SchemaRef> {
390 match &self.state {
391 StreamState::Init => None,
392 StreamState::Ready { schema, .. } => Some(schema),
393 }
394 }
395
396 pub fn schema_bytes(&self) -> Option<&Bytes> {
399 match &self.state {
400 StreamState::Init => None,
401 StreamState::Ready { schema_bytes, .. } => Some(schema_bytes),
402 }
403 }
404
405 fn extract_table_name(mut descriptor: FlightDescriptor) -> Result<String> {
406 ensure!(
407 descriptor.r#type == arrow_flight::flight_descriptor::DescriptorType::Path as i32,
408 InvalidParameterSnafu {
409 reason: "expect FlightDescriptor::type == 'Path' only",
410 }
411 );
412 ensure!(
413 descriptor.path.len() == 1,
414 InvalidParameterSnafu {
415 reason: "expect FlightDescriptor::path has only one table name",
416 }
417 );
418 Ok(descriptor.path.remove(0))
419 }
420}
421
422impl Stream for PutRecordBatchRequestStream {
423 type Item = TonicResult<PutRecordBatchRequest>;
424
425 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
426 loop {
427 let poll = ready!(self.flight_data_stream.poll_next_unpin(cx));
428
429 match poll {
430 Some(Ok(flight_data)) => {
431 let limiter = self.limiter.clone();
432
433 match &mut self.state {
434 StreamState::Init => {
435 let flight_descriptor = match flight_data.flight_descriptor.as_ref() {
437 Some(descriptor) => descriptor.clone(),
438 None => {
439 return Poll::Ready(Some(Err(Status::failed_precondition(
440 "table to put is not found in flight descriptor",
441 ))));
442 }
443 };
444
445 let table_name_str = match Self::extract_table_name(flight_descriptor) {
446 Ok(name) => name,
447 Err(e) => {
448 return Poll::Ready(Some(Err(Status::invalid_argument(
449 e.to_string(),
450 ))));
451 }
452 };
453 let table_name = TableName::new(
454 self.catalog.clone(),
455 self.schema_name.clone(),
456 table_name_str,
457 );
458
459 let mut decoder = FlightDecoder::default();
461 let schema_message = decoder.try_decode(&flight_data).map_err(|e| {
462 Status::invalid_argument(format!("Failed to decode schema: {}", e))
463 })?;
464
465 match schema_message {
466 Some(FlightMessage::Schema(schema)) => {
467 let schema_bytes = decoder.schema_bytes().ok_or_else(|| {
468 Status::internal(
469 "decoder should have schema bytes after decoding schema",
470 )
471 })?;
472
473 self.state = StreamState::Ready {
475 table_name,
476 schema,
477 schema_bytes,
478 decoder,
479 };
480 continue;
482 }
483 _ => {
484 return Poll::Ready(Some(Err(Status::failed_precondition(
485 "first message must be a Schema message",
486 ))));
487 }
488 }
489 }
490 StreamState::Ready {
491 table_name,
492 schema: _,
493 schema_bytes,
494 decoder,
495 } => {
496 let metadata = if !flight_data.app_metadata.is_empty() {
498 serde_json::from_slice::<DoPutMetadata>(&flight_data.app_metadata)
499 .ok()
500 } else {
501 None
502 };
503 let request_id = metadata
504 .as_ref()
505 .map(|meta| meta.request_id())
506 .unwrap_or_default();
507 let timestamp_range = metadata.and_then(|meta| meta.timestamp_range());
508
509 match decoder.try_decode(&flight_data) {
511 Ok(Some(FlightMessage::RecordBatch(record_batch))) => {
512 let table_name = table_name.clone();
513 let schema_bytes = schema_bytes.clone();
514 return Poll::Ready(Some(
515 PutRecordBatchRequest::try_new(
516 table_name,
517 record_batch,
518 request_id,
519 timestamp_range,
520 schema_bytes,
521 flight_data,
522 limiter.as_ref(),
523 )
524 .map_err(|e| Status::invalid_argument(e.to_string())),
525 ));
526 }
527 Ok(Some(other)) => {
528 debug!("Unexpected flight message: {:?}", other);
529 return Poll::Ready(Some(Err(Status::invalid_argument(
530 "Expected RecordBatch message, got other message type",
531 ))));
532 }
533 Ok(None) => {
534 continue;
536 }
537 Err(e) => {
538 return Poll::Ready(Some(Err(Status::invalid_argument(
539 format!("Failed to decode RecordBatch: {}", e),
540 ))));
541 }
542 }
543 }
544 }
545 }
546 Some(Err(e)) => {
547 return Poll::Ready(Some(Err(e)));
548 }
549 None => {
550 return Poll::Ready(None);
551 }
552 }
553 }
554 }
555}
556
557fn extract_flow_extensions(
558 metadata: &tonic::metadata::MetadataMap,
559) -> TonicResult<Vec<(String, String)>> {
560 Ok(extract_json_metadata(metadata, FLOW_EXTENSIONS_METADATA_KEY)?.unwrap_or_default())
561}
562
563fn extract_snapshot_seqs(
564 metadata: &tonic::metadata::MetadataMap,
565) -> TonicResult<HashMap<u64, u64>> {
566 Ok(extract_json_metadata(metadata, SNAPSHOT_SEQS_METADATA_KEY)?.unwrap_or_default())
567}
568
569fn extract_json_metadata<T: serde::de::DeserializeOwned>(
570 metadata: &tonic::metadata::MetadataMap,
571 key: &'static str,
572) -> TonicResult<Option<T>> {
573 let Some(value) = metadata.get(key) else {
574 return Ok(None);
575 };
576
577 let value = value
578 .to_str()
579 .map_err(|e| Status::invalid_argument(format!("Invalid {key} metadata value: {e}")))?;
580
581 let parsed = serde_json::from_str::<T>(value)
582 .map_err(|e| Status::invalid_argument(format!("Invalid {key} metadata JSON: {e}")))?;
583 Ok(Some(parsed))
584}
585
586fn to_flight_data_stream<F>(
587 output: F,
588 tracing_context: TracingContext,
589 flight_compression: FlightCompression,
590 query_ctx: QueryContextRef,
591 should_emit_terminal_metrics: bool,
592) -> TonicStream<FlightData>
593where
594 F: std::future::Future<Output = Result<Output>> + Send + 'static,
595{
596 let initializer = async move {
597 let output = output.await.map_err(Status::from)?;
598 output_to_flight_record_batch_source(output, should_emit_terminal_metrics)
599 };
600 let stream = FlightRecordBatchStream::new(
601 FlightRecordBatchStreamInput::initializer(initializer),
602 tracing_context,
603 flight_compression,
604 query_ctx,
605 );
606 Box::pin(stream) as _
607}
608
609fn output_to_flight_record_batch_source(
610 output: Output,
611 should_emit_terminal_metrics: bool,
612) -> TonicResult<FlightRecordBatchSource> {
613 match output.data {
614 OutputData::Stream(stream) => Ok(FlightRecordBatchSource::RecordBatches(stream)),
615 OutputData::RecordBatches(x) => Ok(FlightRecordBatchSource::RecordBatches(x.as_stream())),
616 OutputData::AffectedRows(rows) => {
617 let terminal_metrics = match terminal_recordbatch_metrics_from_plan_if_requested(
618 output.meta.plan,
619 should_emit_terminal_metrics,
620 ) {
621 Some(metrics) => match serde_json::to_string(&metrics) {
622 Ok(metrics) => Some(metrics),
623 Err(e) => {
624 return Err(Status::internal(format!(
625 "Failed to serialize terminal metrics: {e}"
626 )));
627 }
628 },
629 None => None,
630 };
631 Ok(FlightRecordBatchSource::AffectedRows {
632 rows,
633 metrics: terminal_metrics,
634 })
635 }
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use query::options::{
642 FLOW_INCREMENTAL_AFTER_SEQS, FLOW_INCREMENTAL_MODE, FLOW_RETURN_REGION_SEQ,
643 FLOW_SCHEDULED_TIME_MILLIS, FLOW_SINK_TABLE_ID, FlowIncrementalMode,
644 };
645 use tonic::metadata::{AsciiMetadataValue, MetadataMap};
646
647 use super::*;
648
649 #[test]
650 fn test_extract_flow_extensions_preserves_comma_bearing_values() {
651 let mut metadata = MetadataMap::new();
652 metadata.insert(
653 FLOW_EXTENSIONS_METADATA_KEY,
654 AsciiMetadataValue::try_from(
655 r#"[["flow.return_region_seq","true"],["flow.incremental_after_seqs","{\"1\":10,\"2\":20}"]]"#,
656 )
657 .unwrap(),
658 );
659
660 let extensions = extract_flow_extensions(&metadata).unwrap();
661 assert_eq!(
662 extensions,
663 vec![
664 ("flow.return_region_seq".to_string(), "true".to_string()),
665 (
666 "flow.incremental_after_seqs".to_string(),
667 r#"{"1":10,"2":20}"#.to_string()
668 ),
669 ]
670 );
671 }
672
673 #[test]
674 fn test_flow_extensions_can_carry_scheduled_time() {
675 let mut metadata = MetadataMap::new();
676 metadata.insert(
677 FLOW_EXTENSIONS_METADATA_KEY,
678 AsciiMetadataValue::try_from(r#"[["flow.scheduled_time_millis","1700000000000"]]"#)
679 .unwrap(),
680 );
681
682 let flow_extensions = extract_flow_extensions(&metadata).unwrap();
683 let query_ctx =
684 create_query_context(Channel::Grpc, None, flow_extensions, HashMap::new()).unwrap();
685
686 assert_eq!(
687 query_ctx.extension(FLOW_SCHEDULED_TIME_MILLIS),
688 Some("1700000000000")
689 );
690 }
691
692 #[test]
693 fn test_flow_extensions_forward_sequence_range_to_query_context() {
694 let mut metadata = MetadataMap::new();
695 metadata.insert(
696 FLOW_EXTENSIONS_METADATA_KEY,
697 AsciiMetadataValue::try_from(
698 r#"[["flow.return_region_seq","true"],["flow.incremental_mode","sequence_range"],["flow.incremental_after_seqs","{\"1\":10,\"2\":20}"],["flow.sink_table_id","42"]]"#,
699 )
700 .unwrap(),
701 );
702
703 let flow_extensions = extract_flow_extensions(&metadata).unwrap();
704 let query_ctx =
705 create_query_context(Channel::Grpc, None, flow_extensions, HashMap::new()).unwrap();
706 let parsed =
707 query::options::FlowQueryExtensions::parse_flow_extensions(&query_ctx.extensions())
708 .unwrap()
709 .unwrap();
710
711 assert_eq!(
712 parsed.incremental_mode,
713 Some(FlowIncrementalMode::SequenceRange)
714 );
715 assert_eq!(
716 parsed.incremental_after_seqs,
717 Some(HashMap::from([(1, 10), (2, 20)]))
718 );
719 assert!(parsed.return_region_seq);
720 assert_eq!(parsed.sink_table_id, Some(42));
721 assert_eq!(
722 query_ctx.extension(FLOW_INCREMENTAL_MODE),
723 Some("sequence_range")
724 );
725 assert_eq!(
726 query_ctx.extension(FLOW_INCREMENTAL_AFTER_SEQS),
727 Some(r#"{"1":10,"2":20}"#)
728 );
729 assert_eq!(query_ctx.extension(FLOW_RETURN_REGION_SEQ), Some("true"));
730 assert_eq!(query_ctx.extension(FLOW_SINK_TABLE_ID), Some("42"));
731 }
732
733 #[test]
734 fn test_extract_flow_extensions_rejects_invalid_json() {
735 let mut metadata = MetadataMap::new();
736 metadata.insert(
737 FLOW_EXTENSIONS_METADATA_KEY,
738 AsciiMetadataValue::try_from("not-json").unwrap(),
739 );
740
741 let err = extract_flow_extensions(&metadata).unwrap_err();
742 assert_eq!(err.code(), tonic::Code::InvalidArgument);
743 }
744}