1mod codec;
16
17use std::borrow::Borrow;
18use std::sync::Arc;
19use std::time::Duration;
20
21use api::region::{BatchRegionResponse, RegionResponse};
22use api::v1::ResponseHeader;
23use api::v1::region::RegionResponse as PbRegionResponse;
24use api::v1::region::{
25 RegionRequest, RegionRequestHeader, RemoteDynFilterRequest, RemoteDynFilterUnregister,
26 RemoteDynFilterUpdate, region_request, remote_dyn_filter_request,
27};
28use arc_swap::ArcSwapOption;
29use arrow_flight::Ticket;
30use async_stream::stream;
31use async_trait::async_trait;
32use common_error::ext::{BoxedError, ErrorExt};
33use common_error::status_code::StatusCode;
34use common_grpc::flight::{FlightDecoder, FlightMessage};
35use common_meta::error::{self as meta_error, Result as MetaResult};
36use common_meta::node_manager::Datanode;
37use common_query::request::QueryRequest;
38use common_recordbatch::error::ExternalSnafu;
39use common_recordbatch::{RecordBatch, RecordBatchStreamWrapper, SendableRecordBatchStream};
40use common_telemetry::error;
41use common_telemetry::tracing::Span;
42use common_telemetry::tracing_context::TracingContext;
43use futures_util::Stream;
44use prost::Message;
45use query::query_engine::DefaultSerializer;
46use snafu::{OptionExt, ResultExt, location};
47use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
48use tokio_stream::StreamExt;
49use tonic::codec::CompressionEncoding;
50
51use crate::error::{
52 self, FlightGetSnafu, IllegalDatabaseResponseSnafu, IllegalFlightMessagesSnafu,
53 MissingFieldSnafu, Result,
54};
55use crate::flight::{FlightMessageReader, decode_flight_data};
56use crate::region::codec::RegionCodec;
57use crate::{Client, metrics};
58
59const FLIGHT_DO_GET_TIMEOUT: Duration = Duration::from_secs(10);
60
61#[derive(Debug)]
63pub struct RegionRequester {
64 client: Client,
65 send_compression: bool,
66 accept_compression: bool,
67}
68
69#[async_trait]
70impl Datanode for RegionRequester {
71 async fn handle_request(&self, request: Arc<RegionRequest>) -> MetaResult<RegionResponse> {
72 self.handle_inner(request).await.map_err(Into::into)
73 }
74
75 async fn handle_query(&self, request: QueryRequest) -> MetaResult<SendableRecordBatchStream> {
76 let plan = DFLogicalSubstraitConvertor
77 .encode(&request.plan, DefaultSerializer)
78 .map_err(BoxedError::new)
79 .context(meta_error::ExternalSnafu)?
80 .to_vec();
81 let request = api::v1::region::QueryRequest {
82 header: request.header,
83 region_id: request.region_id.as_u64(),
84 plan,
85 };
86
87 let ticket = Ticket {
88 ticket: request.encode_to_vec().into(),
89 };
90 self.do_get_inner(ticket)
91 .await
92 .map_err(BoxedError::new)
93 .context(meta_error::ExternalSnafu)
94 }
95}
96
97impl RegionRequester {
98 pub fn new(client: Client, send_compression: bool, accept_compression: bool) -> Self {
99 Self {
100 client,
101 send_compression,
102 accept_compression,
103 }
104 }
105
106 pub async fn do_get_inner(&self, ticket: Ticket) -> Result<SendableRecordBatchStream> {
107 let mut flight_client = self
108 .client
109 .make_flight_client(self.send_compression, self.accept_compression)?;
110 let addr = flight_client.addr().to_string();
112 let mut request = tonic::Request::new(ticket);
113 request.set_timeout(FLIGHT_DO_GET_TIMEOUT);
114 let response = flight_client
115 .mut_inner()
116 .do_get(request)
117 .await
118 .or_else(|e| {
119 let tonic_code = e.code();
120 let e: error::Error = e.into();
121 error!(
122 e; "Failed to do Flight get, addr: {}, code: {}",
123 addr,
124 tonic_code
125 );
126 Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
127 addr: addr.clone(),
128 tonic_code,
129 })
130 })?;
131
132 let flight_data_stream = response.into_inner();
133 let mut decoder = FlightDecoder::default();
134
135 let flight_message_stream = flight_data_stream
136 .filter_map(move |flight_data| decode_flight_data(&mut decoder, flight_data));
137
138 recordbatches_from_flight_message_stream(addr, flight_message_stream).await
139 }
140
141 pub async fn handle<R>(&self, request: R) -> Result<RegionResponse>
148 where
149 R: Borrow<RegionRequest> + Send + Sync + 'static,
150 {
151 self.handle_inner(request).await
152 }
153
154 pub async fn handle_remote_dyn_filter_update(
156 &self,
157 query_id: impl Into<String>,
158 update: RemoteDynFilterUpdate,
159 ) -> Result<RegionResponse> {
160 self.handle_inner(build_remote_dyn_filter_update_request(query_id, update))
161 .await
162 }
163
164 pub async fn handle_remote_dyn_filter_unregister(
166 &self,
167 query_id: impl Into<String>,
168 unregister: RemoteDynFilterUnregister,
169 ) -> Result<RegionResponse> {
170 self.handle_inner(build_remote_dyn_filter_unregister_request(
171 query_id, unregister,
172 ))
173 .await
174 }
175
176 async fn handle_inner<R>(&self, request: R) -> Result<RegionResponse>
177 where
178 R: Borrow<RegionRequest> + Send + Sync + 'static,
179 {
180 let request_body = request
181 .borrow()
182 .body
183 .as_ref()
184 .context(MissingFieldSnafu { field: "body" })?;
185 let request_count = match request_body {
186 region_request::Body::Inserts(requests) => Some(requests.requests.len()),
187 region_request::Body::Deletes(requests) => Some(requests.requests.len()),
188 _ => None,
189 };
190 let is_insert = matches!(
191 request_body,
192 region_request::Body::Inserts(_) | region_request::Body::BulkInsert(_)
193 );
194 let request_type = request_body.as_ref().to_string();
195 let _timer = metrics::METRIC_REGION_REQUEST_GRPC
196 .with_label_values(&[request_type.as_str()])
197 .start_timer();
198
199 let (addr, mut client) = self.client.raw_region_client()?;
200 if is_insert {
201 if self.send_compression {
202 client = client.send_compressed(CompressionEncoding::Zstd);
203 }
204 if self.accept_compression {
205 client = client.accept_compressed(CompressionEncoding::Zstd);
206 }
207 }
208
209 let accept_partial_result = request
210 .borrow()
211 .header
212 .as_ref()
213 .and_then(|header| header.query_context.as_ref())
214 .and_then(|context| context.extensions.get(api::region::ACCEPT_PARTIAL_RESULT))
215 .is_some_and(|value| value == "true");
216 let response = async {
217 client
218 .ready()
219 .await
220 .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {e}")))?;
221 let mut request = tonic::Request::new(request);
222 request.extensions_mut().insert(tonic::GrpcMethod::new(
223 "greptime.v1.region.Region",
224 "Handle",
225 ));
226 client
227 .unary(
228 request,
229 tonic::codegen::http::uri::PathAndQuery::from_static(
230 "/greptime.v1.region.Region/Handle",
231 ),
232 RegionCodec::default(),
233 )
234 .await
235 }
236 .await
237 .map_err(|status| map_region_error(status, addr))?
238 .into_inner();
239
240 match request_count {
241 Some(count) if accept_partial_result => decode_write_response(response, count),
242 _ => decode_region_response(response),
243 }
244 }
245}
246
247fn map_region_error(status: tonic::Status, addr: String) -> error::Error {
248 error::Error::RegionServer {
249 addr,
250 code: status.code(),
251 source: BoxedError::new(error::Error::from(status)),
252 location: location!(),
253 }
254}
255
256async fn recordbatches_from_flight_message_stream<S>(
257 addr: String,
258 flight_message_stream: S,
259) -> Result<SendableRecordBatchStream>
260where
261 S: Stream<Item = Result<FlightMessage>> + Send + Unpin + 'static,
262{
263 let mut reader = FlightMessageReader::new(addr.clone(), flight_message_stream);
264 let FlightMessage::Schema(schema) = reader
265 .read_first()
266 .await
267 .map_err(|error| flight_stream_error(reader.remote_addr(), error))?
268 else {
269 return IllegalFlightMessagesSnafu {
270 reason: "Expect schema to be the first flight message",
271 }
272 .fail()
273 .map_err(|error| flight_stream_error(reader.remote_addr(), error));
274 };
275
276 let metrics = Arc::new(ArcSwapOption::from(None));
277 let metrics_ref = metrics.clone();
278
279 let tracing_context = TracingContext::from_current_span();
280
281 let schema =
282 Arc::new(datatypes::schema::Schema::try_from(schema).context(error::ConvertSchemaSnafu)?);
283 let schema_cloned = schema.clone();
284 let stream_addr = addr;
285 let stream = Box::pin(stream!({
286 let _span = tracing_context.attach(common_telemetry::tracing::info_span!(
287 "poll_flight_data_stream"
288 ));
289
290 loop {
291 let flight_message = match reader.read_next().await {
292 Ok(Some(message)) => message,
293 Ok(None) => break,
294 Err(error) => {
295 yield Err(BoxedError::new(flight_stream_error(&stream_addr, error)))
296 .context(ExternalSnafu);
297 break;
298 }
299 };
300
301 match flight_message {
302 FlightMessage::RecordBatch(record_batch) => {
303 yield Ok(RecordBatch::from_df_record_batch(
307 schema_cloned.clone(),
308 record_batch,
309 ));
310 }
311 FlightMessage::Metrics(s) => {
312 match serde_json::from_str(&s) {
314 Ok(metrics) => {
315 metrics_ref.swap(Some(Arc::new(metrics)));
316 }
317 Err(error) => {
318 common_telemetry::warn!(
319 "Failed to decode region Flight metrics: {}",
320 error
321 );
322 }
323 }
324 continue;
325 }
326 _ => {
327 yield IllegalFlightMessagesSnafu {
328 reason: "A Schema message must be succeeded exclusively by a set of RecordBatch messages"
329 }
330 .fail()
331 .map_err(BoxedError::new)
332 .context(ExternalSnafu);
333 break;
334 }
335 }
336 }
337 }));
338 let record_batch_stream = RecordBatchStreamWrapper {
339 schema,
340 stream,
341 output_ordering: None,
342 metrics,
343 span: Span::current(),
344 };
345 Ok(Box::pin(record_batch_stream))
346}
347
348fn flight_stream_error(addr: &str, error: error::Error) -> error::Error {
349 let tonic_code = error.tonic_code().unwrap_or(tonic::Code::Unknown);
350 if error.status_code().should_log_error() {
351 error!(
352 error; "Failed to receive Flight data, addr: {}, code: {}",
353 addr,
354 tonic_code
355 );
356 }
357
358 error::Error::FlightGet {
359 addr: addr.to_string(),
360 tonic_code,
361 source: BoxedError::new(error),
362 }
363}
364
365pub fn build_remote_dyn_filter_update_request(
366 query_id: impl Into<String>,
367 update: RemoteDynFilterUpdate,
368) -> RegionRequest {
369 build_remote_dyn_filter_request(
370 query_id.into(),
371 remote_dyn_filter_request::Action::Update(update),
372 )
373}
374
375pub fn build_remote_dyn_filter_unregister_request(
376 query_id: impl Into<String>,
377 unregister: RemoteDynFilterUnregister,
378) -> RegionRequest {
379 build_remote_dyn_filter_request(
380 query_id.into(),
381 remote_dyn_filter_request::Action::Unregister(unregister),
382 )
383}
384
385fn build_remote_dyn_filter_request(
386 query_id: String,
387 action: remote_dyn_filter_request::Action,
388) -> RegionRequest {
389 RegionRequest {
390 header: Some(RegionRequestHeader {
391 tracing_context: TracingContext::from_current_span().to_w3c(),
392 ..Default::default()
393 }),
394 body: Some(region_request::Body::RemoteDynFilter(
395 RemoteDynFilterRequest {
396 query_id,
397 action: Some(action),
398 },
399 )),
400 }
401}
402
403pub fn check_response_header(header: &Option<ResponseHeader>) -> Result<()> {
405 let status = header
406 .as_ref()
407 .and_then(|header| header.status.as_ref())
408 .context(IllegalDatabaseResponseSnafu {
409 err_msg: "either response header or status is missing",
410 })?;
411
412 if StatusCode::is_success(status.status_code) {
413 Ok(())
414 } else {
415 Err(error::Error::try_from_response_status(status)?)
416 }
417}
418
419pub fn decode_region_response(response: PbRegionResponse) -> Result<RegionResponse> {
421 if !response.failures.is_empty() {
422 return IllegalDatabaseResponseSnafu {
423 err_msg: "Failure details are only valid for inserts/deletes",
424 }
425 .fail();
426 }
427 check_response_header(&response.header)?;
428 Ok(RegionResponse::from_region_response(response))
429}
430
431fn decode_write_response(response: PbRegionResponse, entry_count: usize) -> Result<RegionResponse> {
434 if response.failures.is_empty() {
435 if response
436 .header
437 .as_ref()
438 .and_then(|header| header.status.as_ref())
439 .is_some_and(|status| status.status_code == StatusCode::PartialFailure as u32)
440 {
441 return IllegalDatabaseResponseSnafu {
442 err_msg: "Partial failure has no failed entries",
443 }
444 .fail();
445 }
446 return decode_region_response(response);
447 }
448 let status = response
449 .header
450 .as_ref()
451 .and_then(|header| header.status.as_ref())
452 .context(IllegalDatabaseResponseSnafu {
453 err_msg: "Missing region response status",
454 })?;
455 if status.status_code != StatusCode::PartialFailure as u32 {
456 return IllegalDatabaseResponseSnafu {
457 err_msg: "Failure details require a partial-failure status",
458 }
459 .fail();
460 }
461 let mut seen = std::collections::HashSet::with_capacity(response.failures.len());
462 let mut failures = Vec::with_capacity(response.failures.len());
463 for error in response.failures {
464 let index = error.request_index as usize;
465 if index >= entry_count || !seen.insert(index) {
466 return IllegalDatabaseResponseSnafu {
467 err_msg: "Invalid or duplicate batch error index",
468 }
469 .fail();
470 }
471 let status = error.status.context(IllegalDatabaseResponseSnafu {
472 err_msg: "Missing batch error status",
473 })?;
474 if StatusCode::is_success(status.status_code) {
475 return IllegalDatabaseResponseSnafu {
476 err_msg: "Batch error contains a success status",
477 }
478 .fail();
479 }
480 failures.push((index, error::Error::try_from_response_status(&status)?));
481 }
482 if entry_count == 1
484 && let Some((_, error)) = failures.pop()
485 {
486 return Err(error);
487 }
488 Err(error::Error::PartialRegion {
489 response: Box::new(BatchRegionResponse {
490 affected_rows: response.affected_rows as _,
491 extensions: response.extensions,
492 failures,
493 }),
494 })
495}
496
497#[cfg(test)]
498#[allow(deprecated)]
499mod tests {
500 use std::collections::VecDeque;
501 use std::sync::Mutex;
502
503 use api::v1::ResponseHeader;
504 use api::v1::region::region_server::{Region, RegionServer};
505 use api::v1::region::{
506 BulkInsertRequest, InsertRequest, InsertRequests, RegionRequestFailure,
507 RegionResponse as PbRegionResponse, RemoteDynFilterUnregister, RemoteDynFilterUpdate,
508 region_request, remote_dyn_filter_request,
509 };
510 use api::v1::value::ValueData;
511 use api::v1::{
512 ColumnDataType, ColumnSchema as PbColumnSchema, Row, Rows, Status as PbStatus, Value,
513 };
514 use common_error::ext::RetryHint;
515 use common_error::status_code::StatusCode;
516 use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
517 use common_recordbatch::adapter::RecordBatchMetrics;
518 use datatypes::arrow::array::Int32Array;
519 use datatypes::prelude::{ConcreteDataType, VectorRef};
520 use datatypes::schema::{ColumnSchema, Schema};
521 use datatypes::vectors::Int32Vector;
522 use futures_util::stream;
523 use tokio::net::TcpListener;
524 use tokio::task::JoinHandle;
525 use tokio_stream::wrappers::TcpListenerStream;
526 use tonic::codec::CompressionEncoding;
527 use tonic::{Request, Response, Status};
528
529 use super::*;
530 use crate::Error;
531
532 #[derive(Clone, Default)]
533 struct RecordingRegionService {
534 requests: Arc<Mutex<Vec<(RegionRequest, bool, bool)>>>,
535 responses: Arc<Mutex<VecDeque<std::result::Result<PbRegionResponse, Status>>>>,
536 }
537
538 #[tonic::async_trait]
539 impl Region for RecordingRegionService {
540 async fn handle(
541 &self,
542 request: Request<RegionRequest>,
543 ) -> std::result::Result<Response<PbRegionResponse>, Status> {
544 let metadata = request.metadata();
545 let sends_zstd = metadata.get("grpc-encoding").is_some_and(|v| v == "zstd");
546 let accepts_zstd = metadata
547 .get("grpc-accept-encoding")
548 .and_then(|v| v.to_str().ok())
549 .is_some_and(|v| v.split(',').any(|encoding| encoding == "zstd"));
550 self.requests
551 .lock()
552 .unwrap()
553 .push((request.into_inner(), sends_zstd, accepts_zstd));
554 self.responses
555 .lock()
556 .unwrap()
557 .pop_front()
558 .unwrap_or_else(|| Ok(build_response(StatusCode::Success)))
559 .map(Response::new)
560 }
561 }
562
563 struct RegionTestServer {
564 addr: String,
565 service: RecordingRegionService,
566 task: JoinHandle<()>,
567 }
568
569 impl RegionTestServer {
570 async fn start() -> Self {
571 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
572 let addr = listener.local_addr().unwrap().to_string();
573 let service = RecordingRegionService::default();
574 let server_service = service.clone();
575 let task = tokio::spawn(async move {
576 tonic::transport::Server::builder()
577 .add_service(
578 RegionServer::new(server_service)
579 .accept_compressed(CompressionEncoding::Zstd)
580 .send_compressed(CompressionEncoding::Zstd),
581 )
582 .serve_with_incoming(TcpListenerStream::new(listener))
583 .await
584 .unwrap();
585 });
586 Self {
587 addr,
588 service,
589 task,
590 }
591 }
592
593 fn create_requester(
594 &self,
595 config: ChannelConfig,
596 send: bool,
597 accept: bool,
598 ) -> RegionRequester {
599 RegionRequester::new(
600 Client::with_manager_and_urls(
601 ChannelManager::with_config(config, None),
602 [self.addr.clone()],
603 ),
604 send,
605 accept,
606 )
607 }
608 }
609
610 impl Drop for RegionTestServer {
611 fn drop(&mut self) {
612 self.task.abort();
613 }
614 }
615
616 fn build_response(code: StatusCode) -> PbRegionResponse {
617 PbRegionResponse {
618 header: Some(ResponseHeader {
619 status: Some(PbStatus {
620 status_code: code as u32,
621 ..Default::default()
622 }),
623 }),
624 affected_rows: 1,
625 ..Default::default()
626 }
627 }
628
629 fn build_insert_request() -> RegionRequest {
630 RegionRequest {
631 header: Some(RegionRequestHeader {
632 dbname: "shared-request-test".to_string(),
633 ..Default::default()
634 }),
635 body: Some(region_request::Body::Inserts(InsertRequests {
636 requests: vec![InsertRequest {
637 region_id: 42,
638 rows: Some(Rows {
639 schema: vec![
640 PbColumnSchema {
641 column_name: "message".to_string(),
642 datatype: ColumnDataType::String as i32,
643 ..Default::default()
644 },
645 PbColumnSchema {
646 column_name: "payload".to_string(),
647 datatype: ColumnDataType::Binary as i32,
648 ..Default::default()
649 },
650 ],
651 rows: vec![Row {
652 values: vec![
653 Value {
654 value_data: Some(ValueData::StringValue(
655 "hello 世界".to_string(),
656 )),
657 },
658 Value {
659 value_data: Some(ValueData::BinaryValue(vec![0, 1, 127, 255])),
660 },
661 ],
662 }],
663 }),
664 ..Default::default()
665 }],
666 })),
667 }
668 }
669
670 fn with_partial_result(mut request: RegionRequest) -> RegionRequest {
671 request
672 .header
673 .get_or_insert_with(Default::default)
674 .query_context
675 .get_or_insert_with(Default::default)
676 .extensions
677 .insert(
678 api::region::ACCEPT_PARTIAL_RESULT.to_string(),
679 "true".to_string(),
680 );
681 request
682 }
683
684 #[tokio::test]
685 async fn test_owned_and_shared_requests_preserve_payload_and_compression() {
686 let server = RegionTestServer::start().await;
687 let shared = Arc::new(build_insert_request());
688 for (send, accept) in [(false, false), (true, false), (false, true), (true, true)] {
689 let requester = server.create_requester(ChannelConfig::default(), send, accept);
690 let channel =
691 tonic::transport::Endpoint::from_shared(format!("http://{}", server.addr))
692 .unwrap()
693 .connect()
694 .await
695 .unwrap();
696 let mut baseline = api::v1::region::region_client::RegionClient::new(channel);
697 if send {
698 baseline = baseline.send_compressed(CompressionEncoding::Zstd);
699 }
700 if accept {
701 baseline = baseline.accept_compressed(CompressionEncoding::Zstd);
702 }
703 assert_eq!(
704 baseline
705 .handle(build_insert_request())
706 .await
707 .unwrap()
708 .into_inner()
709 .affected_rows,
710 1
711 );
712 assert_eq!(
713 requester
714 .handle(build_insert_request())
715 .await
716 .unwrap()
717 .affected_rows,
718 1
719 );
720 assert_eq!(
721 requester
722 .handle(Arc::clone(&shared))
723 .await
724 .unwrap()
725 .affected_rows,
726 1
727 );
728 }
729 let requests = server.service.requests.lock().unwrap();
730 assert_eq!(requests.len(), 12);
731 for ((request, sends, accepts), (send, accept)) in requests.iter().zip(
732 [(false, false), (true, false), (false, true), (true, true)]
733 .into_iter()
734 .flat_map(|settings| [settings, settings, settings]),
735 ) {
736 assert_eq!(request, shared.as_ref());
737 assert_eq!((*sends, *accepts), (send, accept));
738 }
739 }
740
741 #[tokio::test]
742 async fn test_partial_result_requires_explicit_acceptance() {
743 let server = RegionTestServer::start().await;
744 let requester = server.create_requester(ChannelConfig::default(), false, false);
745 for accepted in [false, true] {
746 let request = if accepted {
747 with_partial_result(build_insert_request())
748 } else {
749 build_insert_request()
750 };
751 let shared = Arc::new(request);
752 server
753 .service
754 .responses
755 .lock()
756 .unwrap()
757 .push_back(Ok(PbRegionResponse {
758 header: Some(ResponseHeader {
759 status: Some(PbStatus {
760 status_code: StatusCode::PartialFailure as u32,
761 ..Default::default()
762 }),
763 }),
764 failures: vec![RegionRequestFailure {
765 request_index: 0,
766 status: Some(PbStatus {
767 status_code: StatusCode::RegionNotFound as u32,
768 retry_hint: RetryHint::RetryAfterRouteRefresh.as_str().to_string(),
769 ..Default::default()
770 }),
771 }],
772 ..Default::default()
773 }));
774 let error = requester.handle(Arc::clone(&shared)).await.unwrap_err();
775 if accepted {
776 assert_eq!(error.status_code(), StatusCode::RegionNotFound);
777 assert_eq!(error.retry_hint(), RetryHint::RetryAfterRouteRefresh);
778 } else {
779 assert!(matches!(error, Error::IllegalDatabaseResponse { .. }));
780 }
781 assert_eq!(
782 &server.service.requests.lock().unwrap().last().unwrap().0,
783 shared.as_ref()
784 );
785 assert_eq!(Arc::strong_count(&shared), 1);
786 }
787 }
788
789 #[tokio::test]
790 async fn test_shared_request_can_be_resent_after_transport_error() {
791 let server = RegionTestServer::start().await;
792 let mut status = Status::unavailable("migrating");
793 status.metadata_mut().insert(
794 common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
795 RetryHint::Retryable.as_str().parse().unwrap(),
796 );
797 server
798 .service
799 .responses
800 .lock()
801 .unwrap()
802 .push_back(Err(status));
803 let requester = server.create_requester(ChannelConfig::default(), false, false);
804 let shared = Arc::new(build_insert_request());
805 let error = requester.handle(Arc::clone(&shared)).await.unwrap_err();
806 assert_eq!(error.retry_hint(), RetryHint::Retryable);
807 assert!(
808 matches!(error, Error::RegionServer { addr, code: tonic::Code::Unavailable, .. } if addr == server.addr)
809 );
810 requester.handle(Arc::clone(&shared)).await.unwrap();
811 let requests = server.service.requests.lock().unwrap();
812 assert_eq!(requests.len(), 2);
813 assert_eq!(&requests[0].0, shared.as_ref());
814 assert_eq!(&requests[1].0, shared.as_ref());
815 }
816
817 #[test]
818 fn test_retry_hint_wire_compatibility() {
819 for (name, wire_hint, expected_hint) in [
820 ("legacy_missing", None, RetryHint::NonRetryable),
821 ("legacy_retryable", Some("retryable"), RetryHint::Retryable),
822 (
823 "legacy_non_retryable",
824 Some("non_retryable"),
825 RetryHint::NonRetryable,
826 ),
827 (
828 "route_refresh",
829 Some("retry_after_route_refresh"),
830 RetryHint::RetryAfterRouteRefresh,
831 ),
832 (
833 "unknown_future_hint",
834 Some("future_retry_hint"),
835 RetryHint::NonRetryable,
836 ),
837 ] {
838 let mut status = Status::failed_precondition("region unavailable");
839 status.metadata_mut().insert(
840 common_error::GREPTIME_DB_HEADER_ERROR_CODE,
841 (StatusCode::RegionNotReady as u32)
842 .to_string()
843 .parse()
844 .unwrap(),
845 );
846 if let Some(hint) = wire_hint {
847 status.metadata_mut().insert(
848 common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
849 hint.parse().unwrap(),
850 );
851 }
852 let error: Error = status.into();
853 assert_eq!(error.retry_hint(), expected_hint, "{name}");
854 assert_eq!(error.status_code(), StatusCode::RegionNotReady, "{name}");
855 assert_eq!(
856 error.tonic_code(),
857 Some(tonic::Code::FailedPrecondition),
858 "{name}"
859 );
860 }
861 }
862
863 #[tokio::test]
864 async fn test_transport_error_parity_and_datanode_mapping() {
865 let server = RegionTestServer::start().await;
866 let requester = server.create_requester(ChannelConfig::default(), false, false);
867 for hint in [
868 RetryHint::Retryable,
869 RetryHint::RetryAfterRouteRefresh,
870 RetryHint::NonRetryable,
871 ] {
872 let mut status = Status::failed_precondition("region unavailable");
873 status.metadata_mut().insert(
874 common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
875 hint.as_str().parse().unwrap(),
876 );
877 status.metadata_mut().insert(
878 common_error::GREPTIME_DB_HEADER_ERROR_CODE,
879 (StatusCode::RegionNotFound as u32)
880 .to_string()
881 .parse()
882 .unwrap(),
883 );
884 server
885 .service
886 .responses
887 .lock()
888 .unwrap()
889 .extend((0..4).map(|_| Err(status.clone())));
890 let mut baseline = api::v1::region::region_client::RegionClient::connect(format!(
891 "http://{}",
892 server.addr
893 ))
894 .await
895 .unwrap();
896 let baseline: Error = baseline
897 .handle(build_insert_request())
898 .await
899 .unwrap_err()
900 .into();
901 assert_eq!(baseline.status_code(), StatusCode::RegionNotFound);
902 assert_eq!(baseline.retry_hint(), hint);
903 assert_eq!(baseline.tonic_code(), Some(tonic::Code::FailedPrecondition));
904 let owned = requester.handle(build_insert_request()).await.unwrap_err();
905 let shared = requester
906 .handle(Arc::new(build_insert_request()))
907 .await
908 .unwrap_err();
909 for error in [owned, shared] {
910 assert_eq!(error.status_code(), baseline.status_code());
911 assert_eq!(error.retry_hint(), baseline.retry_hint());
912 assert_eq!(error.tonic_code(), baseline.tonic_code());
913 }
914 let datanode: &dyn Datanode = &requester;
915 let error = datanode
916 .handle_request(Arc::new(build_insert_request()))
917 .await
918 .unwrap_err();
919 assert_eq!(error.status_code(), StatusCode::RegionNotFound);
920 assert_eq!(error.retry_hint(), hint);
921 assert!(matches!(error, meta_error::Error::External { .. }));
922 }
923 }
924
925 #[tokio::test]
926 async fn test_datanode_transport_retry_hints() {
927 let server = RegionTestServer::start().await;
928 let requester = server.create_requester(ChannelConfig::default(), false, false);
929 let datanode: &dyn Datanode = &requester;
930 let statuses = [
931 tonic::Code::Unavailable,
932 tonic::Code::Cancelled,
933 tonic::Code::DeadlineExceeded,
934 tonic::Code::FailedPrecondition,
935 ]
936 .into_iter()
937 .flat_map(|code| {
938 [
939 None,
940 Some(RetryHint::NonRetryable),
941 Some(RetryHint::Retryable),
942 Some(RetryHint::RetryAfterRouteRefresh),
943 ]
944 .into_iter()
945 .map(move |hint| (code, hint))
946 });
947 for (code, hint) in statuses {
948 for body in [
949 region_request::Body::Inserts(Default::default()),
950 region_request::Body::Deletes(Default::default()),
951 region_request::Body::BulkInsert(Default::default()),
952 region_request::Body::Create(Default::default()),
953 region_request::Body::RemoteDynFilter(Default::default()),
954 ] {
955 let request_type = body.as_ref().to_string();
956 let mut status = Status::new(code, "request failed");
957 status.metadata_mut().insert(
959 common_error::GREPTIME_DB_HEADER_ERROR_CODE,
960 (StatusCode::RegionNotReady as u32)
961 .to_string()
962 .parse()
963 .unwrap(),
964 );
965 if let Some(hint) = hint {
966 status.metadata_mut().insert(
967 common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
968 hint.as_str().parse().unwrap(),
969 );
970 }
971 let original = Error::from(status.clone());
972 let expected = if original.should_retry() && !original.retry_hint().is_retryable() {
973 RetryHint::Retryable
974 } else {
975 original.retry_hint()
976 };
977 server
978 .service
979 .responses
980 .lock()
981 .unwrap()
982 .extend([Err(status.clone()), Err(status)]);
983 let request = RegionRequest {
984 header: None,
985 body: Some(body),
986 };
987 let shared = Arc::new(request);
988 let shared_error = datanode
989 .handle_request(Arc::clone(&shared))
990 .await
991 .unwrap_err();
992 assert_eq!(Arc::strong_count(&shared), 1);
993 let request = Arc::try_unwrap(shared).unwrap();
994 let owned_error = datanode
995 .handle_request(Arc::new(request))
996 .await
997 .unwrap_err();
998 for error in [owned_error, shared_error] {
999 assert_eq!(
1000 error.retry_hint(),
1001 expected,
1002 "{code:?}, {hint:?}, {request_type}"
1003 );
1004 assert_eq!(error.retry_hint().is_retryable(), original.should_retry());
1005 assert_eq!(error.status_code(), StatusCode::RegionNotReady);
1006 let meta_error::Error::External { source, .. } = error else {
1007 panic!("expected an external error");
1008 };
1009 assert_eq!(source.retry_hint(), expected);
1010 let Error::RegionServer {
1011 source,
1012 code: actual_code,
1013 ..
1014 } = source.as_any().downcast_ref::<Error>().unwrap()
1015 else {
1016 panic!("expected a region server error");
1017 };
1018 assert_eq!(*actual_code, code);
1019 assert_eq!(source.retry_hint(), original.retry_hint());
1020 assert_eq!(source.to_string(), original.to_string());
1021 }
1022 }
1023 }
1024 }
1025
1026 #[tokio::test]
1027 async fn test_shared_request_preserves_response_header_errors() {
1028 let server = RegionTestServer::start().await;
1029 server.service.responses.lock().unwrap().extend([
1030 Ok(PbRegionResponse::default()),
1031 Ok(build_response(StatusCode::RegionNotFound)),
1032 ]);
1033 let requester = server.create_requester(ChannelConfig::default(), false, false);
1034 let shared = Arc::new(build_insert_request());
1035 assert!(matches!(
1036 requester.handle(Arc::clone(&shared)).await.unwrap_err(),
1037 Error::IllegalDatabaseResponse { .. }
1038 ));
1039 assert!(matches!(
1040 requester.handle(shared).await.unwrap_err(),
1041 Error::Server {
1042 code: StatusCode::RegionNotFound,
1043 ..
1044 }
1045 ));
1046 }
1047
1048 #[tokio::test]
1049 async fn test_shared_request_respects_configured_message_limits() {
1050 let server = RegionTestServer::start().await;
1051 let shared = Arc::new(build_insert_request());
1052 for send_limit in [true, false] {
1053 let mut config = ChannelConfig::default();
1054 if send_limit {
1055 config.max_send_message_size = "1B".parse().unwrap();
1056 } else {
1057 config.max_recv_message_size = "1B".parse().unwrap();
1058 }
1059 let mut baseline = api::v1::region::region_client::RegionClient::connect(format!(
1060 "http://{}",
1061 server.addr
1062 ))
1063 .await
1064 .unwrap()
1065 .max_encoding_message_size(config.max_send_message_size.as_bytes() as usize)
1066 .max_decoding_message_size(config.max_recv_message_size.as_bytes() as usize);
1067 let baseline: Error = baseline
1068 .handle(build_insert_request())
1069 .await
1070 .unwrap_err()
1071 .into();
1072 if !send_limit {
1073 assert_eq!(
1074 baseline.tonic_code(),
1075 Some(tonic::Code::OutOfRange),
1076 "{baseline:?}"
1077 );
1078 }
1079 let requester = server.create_requester(config, false, false);
1080 let error = requester.handle(Arc::clone(&shared)).await.unwrap_err();
1081 assert_eq!(
1082 error.tonic_code(),
1083 baseline.tonic_code(),
1084 "shared: {error:?}; generated: {baseline:?}"
1085 );
1086 assert_eq!(
1087 error.status_code(),
1088 baseline.status_code(),
1089 "shared: {error:?}; generated: {baseline:?}"
1090 );
1091 let Error::RegionServer { source, .. } = &error else {
1092 panic!("expected a region server error: {error:?}");
1093 };
1094 assert_eq!(
1095 source.retry_hint(),
1096 baseline.retry_hint(),
1097 "shared: {error:?}; generated: {baseline:?}"
1098 );
1099 assert_eq!(
1100 server.service.requests.lock().unwrap().len(),
1101 if send_limit { 0 } else { 2 }
1102 );
1103 }
1104 let requester = server.create_requester(ChannelConfig::default(), false, false);
1105 requester.handle(shared).await.unwrap();
1106 }
1107
1108 #[tokio::test]
1109 async fn test_inserts_and_bulk_insert_use_transport_compression() {
1110 let server = RegionTestServer::start().await;
1111 let requester = server.create_requester(ChannelConfig::default(), true, true);
1112 let send_only_requester = server.create_requester(ChannelConfig::default(), true, false);
1113 let accept_only_requester = server.create_requester(ChannelConfig::default(), false, true);
1114 let disabled_requester = server.create_requester(ChannelConfig::default(), false, false);
1115 let inserts = || RegionRequest {
1116 body: Some(region_request::Body::Inserts(Default::default())),
1117 ..Default::default()
1118 };
1119 let bulk_insert = || RegionRequest {
1120 body: Some(region_request::Body::BulkInsert(
1121 BulkInsertRequest::default(),
1122 )),
1123 ..Default::default()
1124 };
1125
1126 requester.handle(inserts()).await.unwrap();
1127 send_only_requester.handle(inserts()).await.unwrap();
1128 accept_only_requester.handle(inserts()).await.unwrap();
1129 requester.handle(bulk_insert()).await.unwrap();
1130 disabled_requester.handle(bulk_insert()).await.unwrap();
1131 requester
1132 .handle(build_remote_dyn_filter_unregister_request(
1133 "query-1",
1134 RemoteDynFilterUnregister {
1135 filter_id: "filter-1".to_string(),
1136 },
1137 ))
1138 .await
1139 .unwrap();
1140
1141 assert_eq!(
1142 vec![
1143 (true, true),
1144 (true, false),
1145 (false, true),
1146 (true, true),
1147 (false, false),
1148 (false, false)
1149 ],
1150 server
1151 .service
1152 .requests
1153 .lock()
1154 .unwrap()
1155 .iter()
1156 .map(|(_, send, accept)| (*send, *accept))
1157 .collect::<Vec<_>>()
1158 );
1159 }
1160
1161 #[test]
1162 fn test_flight_stream_error_preserves_peer_address() {
1163 let error = flight_stream_error(
1164 "127.0.0.1:4001",
1165 tonic::Status::unavailable("datanode unavailable").into(),
1166 );
1167
1168 assert!(matches!(
1169 error,
1170 error::Error::FlightGet {
1171 addr,
1172 tonic_code: tonic::Code::Unavailable,
1173 ..
1174 } if addr == "127.0.0.1:4001"
1175 ));
1176 }
1177
1178 #[tokio::test]
1179 async fn test_empty_flight_stream_preserves_peer_address() {
1180 let Err(error) = recordbatches_from_flight_message_stream(
1181 "127.0.0.1:4001".to_string(),
1182 stream::empty::<Result<FlightMessage>>(),
1183 )
1184 .await
1185 else {
1186 panic!("expected empty Flight stream to fail");
1187 };
1188
1189 assert!(matches!(
1190 error,
1191 error::Error::FlightGet {
1192 addr,
1193 tonic_code: tonic::Code::Unknown,
1194 ..
1195 } if addr == "127.0.0.1:4001"
1196 ));
1197 }
1198
1199 fn test_schema() -> Arc<Schema> {
1200 Arc::new(Schema::new(vec![ColumnSchema::new(
1201 "v",
1202 ConcreteDataType::int32_datatype(),
1203 false,
1204 )]))
1205 }
1206
1207 fn test_metrics_json() -> String {
1208 serde_json::to_string(&RecordBatchMetrics {
1209 elapsed_compute: 7,
1210 ..Default::default()
1211 })
1212 .unwrap()
1213 }
1214
1215 #[tokio::test]
1216 async fn test_partial_response_validation_and_compression() {
1217 let server = RegionTestServer::start().await;
1218 let requester = server.create_requester(ChannelConfig::default(), true, true);
1219 let mut request = build_insert_request();
1220 if let Some(region_request::Body::Inserts(inserts)) = &mut request.body {
1221 inserts.requests.push(inserts.requests[0].clone());
1222 }
1223 let request = Arc::new(with_partial_result(request));
1224 let failure = RegionRequestFailure {
1225 request_index: 0,
1226 status: Some(PbStatus {
1227 status_code: StatusCode::RegionNotFound as u32,
1228 err_msg: "moved".to_string(),
1229 retry_hint: "retry_after_route_refresh".to_string(),
1230 }),
1231 };
1232 for (failures, valid) in [
1233 (vec![], true),
1234 (vec![failure.clone()], true),
1235 (vec![failure.clone(), failure.clone()], false),
1236 (
1237 vec![RegionRequestFailure {
1238 request_index: u32::MAX,
1239 ..failure.clone()
1240 }],
1241 false,
1242 ),
1243 (
1244 vec![RegionRequestFailure {
1245 request_index: 0,
1246 status: None,
1247 }],
1248 false,
1249 ),
1250 (
1251 vec![RegionRequestFailure {
1252 request_index: 0,
1253 status: Some(PbStatus::default()),
1254 }],
1255 false,
1256 ),
1257 (
1258 vec![RegionRequestFailure {
1259 request_index: 0,
1260 status: Some(PbStatus {
1261 status_code: u32::MAX,
1262 ..Default::default()
1263 }),
1264 }],
1265 false,
1266 ),
1267 ] {
1268 let has_error = !failures.is_empty();
1269 server
1270 .service
1271 .responses
1272 .lock()
1273 .unwrap()
1274 .push_back(Ok(PbRegionResponse {
1275 header: Some(ResponseHeader {
1276 status: Some(PbStatus {
1277 status_code: if has_error {
1278 StatusCode::PartialFailure
1279 } else {
1280 StatusCode::Success
1281 } as u32,
1282 ..Default::default()
1283 }),
1284 }),
1285 affected_rows: 3,
1286 extensions: std::collections::HashMap::from([("test".to_string(), vec![1, 2])]),
1287 failures,
1288 ..Default::default()
1289 }));
1290 let response = requester.handle_inner(Arc::clone(&request)).await;
1291 if valid {
1292 if has_error {
1293 let error = response.unwrap_err();
1294 assert!(error.output_msg().contains("entry 0"));
1295 assert!(error.output_msg().contains("moved"));
1296 let error: common_meta::error::Error = error.into();
1297 assert!(error.output_msg().contains("moved"));
1298 let common_meta::error::Error::PartialRegion { response } = error else {
1299 panic!("Expected partial region failure");
1300 };
1301 assert_eq!(response.affected_rows, 3);
1302 assert_eq!(response.extensions["test"], vec![1, 2]);
1303 assert_eq!(
1304 response.failures[0].1.retry_hint(),
1305 RetryHint::RetryAfterRouteRefresh
1306 );
1307 } else {
1308 let response = response.unwrap();
1309 assert_eq!(response.affected_rows, 3);
1310 assert_eq!(response.extensions["test"], vec![1, 2]);
1311 }
1312 } else {
1313 assert!(matches!(
1314 response.unwrap_err(),
1315 Error::IllegalDatabaseResponse { .. }
1316 ));
1317 }
1318 }
1319 for (received, send, accept) in server.service.requests.lock().unwrap().iter() {
1320 assert_eq!(received, request.as_ref());
1321 assert!(*send && *accept);
1322 }
1323 assert_eq!(Arc::strong_count(&request), 1);
1324 }
1325
1326 #[test]
1327 fn test_partial_response_envelope() {
1328 let failure = RegionRequestFailure {
1329 request_index: 0,
1330 status: Some(PbStatus {
1331 status_code: StatusCode::RegionNotFound as u32,
1332 retry_hint: RetryHint::RetryAfterRouteRefresh.as_str().to_string(),
1333 ..Default::default()
1334 }),
1335 };
1336 for (status, failures, count) in [
1337 (Some(StatusCode::Success), vec![failure.clone()], Some(2)),
1338 (Some(StatusCode::PartialFailure), vec![], Some(2)),
1339 (
1340 Some(StatusCode::PartialFailure),
1341 vec![failure.clone()],
1342 None,
1343 ),
1344 (None, vec![failure.clone()], Some(2)),
1345 ] {
1346 let response = PbRegionResponse {
1347 header: status.map(|code| ResponseHeader {
1348 status: Some(PbStatus {
1349 status_code: code as u32,
1350 ..Default::default()
1351 }),
1352 }),
1353 failures,
1354 ..Default::default()
1355 };
1356 assert!(matches!(
1357 match count {
1358 Some(count) => decode_write_response(response, count),
1359 None => decode_region_response(response),
1360 },
1361 Err(Error::IllegalDatabaseResponse { .. })
1362 ));
1363 }
1364 let response = PbRegionResponse {
1365 header: Some(ResponseHeader {
1366 status: Some(PbStatus {
1367 status_code: StatusCode::PartialFailure as u32,
1368 ..Default::default()
1369 }),
1370 }),
1371 failures: vec![failure],
1372 ..Default::default()
1373 };
1374 assert!(check_response_header(&response.header).is_err());
1376 let error = decode_write_response(response, 1).unwrap_err();
1377 assert_eq!(error.status_code(), StatusCode::RegionNotFound);
1378 assert_eq!(error.retry_hint(), RetryHint::RetryAfterRouteRefresh);
1379 }
1380
1381 #[test]
1382 fn test_partial_error_preserves_status_and_retry_hint() {
1383 let expected_message = "Region request has 2 failed entries; entry 0: first input failed";
1384 for code in [StatusCode::StorageUnavailable, StatusCode::InvalidArguments] {
1385 let response = PbRegionResponse {
1386 header: Some(ResponseHeader {
1387 status: Some(PbStatus {
1388 status_code: StatusCode::PartialFailure as u32,
1389 ..Default::default()
1390 }),
1391 }),
1392 failures: vec![
1393 RegionRequestFailure {
1394 request_index: 1,
1395 status: Some(PbStatus {
1396 status_code: StatusCode::Internal as u32,
1397 err_msg: "later input failed".to_string(),
1398 ..Default::default()
1399 }),
1400 },
1401 RegionRequestFailure {
1402 request_index: 0,
1403 status: Some(PbStatus {
1404 status_code: code as u32,
1405 err_msg: "first input failed".to_string(),
1406 retry_hint: RetryHint::NonRetryable.as_str().to_string(),
1407 }),
1408 },
1409 ],
1410 ..Default::default()
1411 };
1412 let error = decode_write_response(response, 2).unwrap_err();
1413 assert_eq!(code, error.status_code());
1414 assert_eq!(expected_message, error.to_string());
1415 assert_eq!(RetryHint::Retryable, error.retry_hint());
1416 let error: common_meta::error::Error = error.into();
1417 assert_eq!(code, error.status_code());
1418 assert_eq!(expected_message, error.to_string());
1419 assert_eq!(RetryHint::Retryable, error.retry_hint());
1420 let common_meta::error::Error::PartialRegion { response } = error else {
1421 panic!("expected partial result")
1422 };
1423 assert!(
1424 response
1425 .failures
1426 .iter()
1427 .all(|(_, error)| error.retry_hint() == RetryHint::NonRetryable)
1428 );
1429 }
1430 }
1431
1432 #[tokio::test]
1433 async fn test_region_rpc_errors() {
1434 let server = RegionTestServer::start().await;
1435 let requester = server.create_requester(ChannelConfig::default(), false, false);
1436 let request = Arc::new(build_insert_request());
1437 let mut business_error = Status::unimplemented("unsupported write");
1438 business_error.metadata_mut().insert(
1439 common_error::GREPTIME_DB_HEADER_ERROR_CODE,
1440 (StatusCode::Unsupported as u32)
1441 .to_string()
1442 .parse()
1443 .unwrap(),
1444 );
1445 for status in [
1446 Status::unimplemented("unknown method"),
1447 business_error,
1448 Status::unavailable("lost response"),
1449 Status::deadline_exceeded("timeout"),
1450 ] {
1451 let code = status.code();
1452 server
1453 .service
1454 .responses
1455 .lock()
1456 .unwrap()
1457 .push_back(Err(status));
1458 let error = requester
1459 .handle_inner(Arc::clone(&request))
1460 .await
1461 .unwrap_err();
1462 assert!(matches!(error, Error::RegionServer { code: actual, .. } if actual == code));
1463 }
1464 assert_eq!(server.service.requests.lock().unwrap().len(), 4);
1465 }
1466
1467 #[test]
1468 fn test_check_response_header() {
1469 for (hint, expected) in [
1470 ("retryable", RetryHint::Retryable),
1471 (
1472 "retry_after_route_refresh",
1473 RetryHint::RetryAfterRouteRefresh,
1474 ),
1475 ("non_retryable", RetryHint::NonRetryable),
1476 ("", RetryHint::NonRetryable),
1477 ("future_hint", RetryHint::NonRetryable),
1478 ] {
1479 let error = check_response_header(&Some(ResponseHeader {
1480 status: Some(PbStatus {
1481 status_code: StatusCode::RegionNotFound as u32,
1482 err_msg: "region moved".to_string(),
1483 retry_hint: hint.to_string(),
1484 }),
1485 }))
1486 .unwrap_err();
1487 assert_eq!(error.retry_hint(), expected, "{hint}");
1488 }
1489
1490 let result = check_response_header(&None);
1491 assert!(matches!(
1492 result.unwrap_err(),
1493 Error::IllegalDatabaseResponse { .. }
1494 ));
1495
1496 let result = check_response_header(&Some(ResponseHeader { status: None }));
1497 assert!(matches!(
1498 result.unwrap_err(),
1499 Error::IllegalDatabaseResponse { .. }
1500 ));
1501
1502 let result = check_response_header(&Some(ResponseHeader {
1503 status: Some(PbStatus {
1504 status_code: StatusCode::Success as u32,
1505 err_msg: String::default(),
1506 ..Default::default()
1507 }),
1508 }));
1509 assert!(result.is_ok());
1510
1511 let result = check_response_header(&Some(ResponseHeader {
1512 status: Some(PbStatus {
1513 status_code: u32::MAX,
1514 err_msg: String::default(),
1515 ..Default::default()
1516 }),
1517 }));
1518 assert!(matches!(
1519 result.unwrap_err(),
1520 Error::IllegalDatabaseResponse { .. }
1521 ));
1522
1523 let result = check_response_header(&Some(ResponseHeader {
1524 status: Some(PbStatus {
1525 status_code: StatusCode::Internal as u32,
1526 err_msg: "blabla".to_string(),
1527 ..Default::default()
1528 }),
1529 }));
1530 let Error::Server { code, msg, .. } = result.unwrap_err() else {
1531 unreachable!()
1532 };
1533 assert_eq!(code, StatusCode::Internal);
1534 assert_eq!(msg, "blabla");
1535 }
1536
1537 #[test]
1538 fn test_build_remote_dyn_filter_request_sets_header_and_body() {
1539 let request = build_remote_dyn_filter_update_request(
1540 "query-1",
1541 RemoteDynFilterUpdate {
1542 filter_id: "filter-1".to_string(),
1543 payload: vec![1, 2, 3],
1544 generation: 7,
1545 is_complete: false,
1546 },
1547 );
1548
1549 request.header.expect("remote dyn filter header must exist");
1550
1551 let body = request.body.expect("remote dyn filter body must exist");
1552 let region_request::Body::RemoteDynFilter(remote_request) = body else {
1553 panic!("expected remote dyn filter request body");
1554 };
1555
1556 assert_eq!(remote_request.query_id, "query-1");
1557 assert!(matches!(
1558 remote_request.action,
1559 Some(remote_dyn_filter_request::Action::Update(_))
1560 ));
1561 }
1562
1563 #[test]
1564 fn test_build_remote_dyn_filter_unregister_request_sets_header_and_body() {
1565 let request = build_remote_dyn_filter_unregister_request(
1566 "query-1",
1567 RemoteDynFilterUnregister {
1568 filter_id: "filter-9".to_string(),
1569 },
1570 );
1571
1572 request.header.expect("remote dyn filter header must exist");
1573
1574 let body = request.body.expect("remote dyn filter body must exist");
1575 let region_request::Body::RemoteDynFilter(remote_request) = body else {
1576 panic!("expected remote dyn filter request body");
1577 };
1578
1579 assert_eq!(remote_request.query_id, "query-1");
1580 assert!(matches!(
1581 remote_request.action,
1582 Some(remote_dyn_filter_request::Action::Unregister(_))
1583 ));
1584 }
1585
1586 #[tokio::test]
1587 async fn test_record_batch_stream_continues_after_pre_batch_metrics() {
1588 let schema = test_schema();
1589 let batch = RecordBatch::new(
1590 schema.clone(),
1591 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1592 )
1593 .unwrap();
1594
1595 let mut recordbatches = recordbatches_from_flight_message_stream(
1596 "test-peer".to_string(),
1597 stream::iter(vec![
1598 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1599 Ok(FlightMessage::Metrics(test_metrics_json())),
1600 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1601 ]),
1602 )
1603 .await
1604 .unwrap();
1605
1606 let batch = recordbatches.next().await.unwrap().unwrap();
1607 assert_eq!(batch.num_rows(), 1);
1608 assert!(recordbatches.next().await.is_none());
1609
1610 let metrics = recordbatches.metrics().unwrap();
1611 assert_eq!(metrics.elapsed_compute, 7);
1612 }
1613
1614 #[tokio::test]
1615 async fn test_record_batch_is_yielded_without_waiting_for_next_message() {
1616 let schema = test_schema();
1617 let batch = RecordBatch::new(
1618 schema.clone(),
1619 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1620 )
1621 .unwrap();
1622
1623 let messages = stream::iter(vec![
1624 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1625 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1626 ])
1627 .chain(stream::pending::<Result<FlightMessage>>());
1628 let mut recordbatches =
1629 recordbatches_from_flight_message_stream("test-peer".to_string(), messages)
1630 .await
1631 .unwrap();
1632
1633 let batch = tokio::time::timeout(Duration::from_secs(1), recordbatches.next())
1634 .await
1635 .expect("the first batch must not wait for lookahead")
1636 .unwrap()
1637 .unwrap();
1638 assert_eq!(batch.num_rows(), 1);
1639 }
1640
1641 #[tokio::test]
1642 async fn test_malformed_region_metrics_are_non_fatal() {
1643 let schema = test_schema();
1644 let batch = RecordBatch::new(
1645 schema.clone(),
1646 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1647 )
1648 .unwrap();
1649 let mut recordbatches = recordbatches_from_flight_message_stream(
1650 "test-peer".to_string(),
1651 stream::iter(vec![
1652 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1653 Ok(FlightMessage::Metrics("{not-json}".to_string())),
1654 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1655 ]),
1656 )
1657 .await
1658 .unwrap();
1659
1660 assert_eq!(recordbatches.next().await.unwrap().unwrap().num_rows(), 1);
1661 assert!(recordbatches.next().await.is_none());
1662 }
1663
1664 #[tokio::test]
1665 async fn test_record_batch_stream_preserves_following_record_batch() {
1666 let schema = test_schema();
1667 let first_batch = RecordBatch::new(
1668 schema.clone(),
1669 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1670 )
1671 .unwrap();
1672 let second_batch = RecordBatch::new(
1673 schema.clone(),
1674 vec![Arc::new(Int32Vector::from_slice([2])) as VectorRef],
1675 )
1676 .unwrap();
1677
1678 let mut recordbatches = recordbatches_from_flight_message_stream(
1679 "test-peer".to_string(),
1680 stream::iter(vec![
1681 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1682 Ok(FlightMessage::RecordBatch(
1683 first_batch.into_df_record_batch(),
1684 )),
1685 Ok(FlightMessage::RecordBatch(
1686 second_batch.into_df_record_batch(),
1687 )),
1688 ]),
1689 )
1690 .await
1691 .unwrap();
1692
1693 let first_batch = recordbatches.next().await.unwrap().unwrap();
1694 let second_batch = recordbatches.next().await.unwrap().unwrap();
1695 assert_eq!(
1696 first_batch
1697 .column(0)
1698 .as_any()
1699 .downcast_ref::<Int32Array>()
1700 .unwrap()
1701 .value(0),
1702 1
1703 );
1704 assert_eq!(
1705 second_batch
1706 .column(0)
1707 .as_any()
1708 .downcast_ref::<Int32Array>()
1709 .unwrap()
1710 .value(0),
1711 2
1712 );
1713 assert!(recordbatches.next().await.is_none());
1714 }
1715
1716 #[tokio::test]
1717 async fn test_record_batch_stream_captures_final_metrics_after_record_batch() {
1718 let schema = test_schema();
1719 let batch = RecordBatch::new(
1720 schema.clone(),
1721 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1722 )
1723 .unwrap();
1724 let final_metrics = serde_json::to_string(&RecordBatchMetrics {
1725 elapsed_compute: 99,
1726 ..Default::default()
1727 })
1728 .unwrap();
1729 let mut recordbatches = recordbatches_from_flight_message_stream(
1730 "test-peer".to_string(),
1731 stream::iter(vec![
1732 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1733 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1734 Ok(FlightMessage::Metrics(final_metrics)),
1735 ]),
1736 )
1737 .await
1738 .unwrap();
1739
1740 assert_eq!(recordbatches.next().await.unwrap().unwrap().num_rows(), 1);
1741 assert!(recordbatches.next().await.is_none());
1742 assert_eq!(recordbatches.metrics().unwrap().elapsed_compute, 99);
1743 }
1744
1745 #[tokio::test]
1746 async fn test_record_batch_stream_exposes_error_after_pre_batch_metrics() {
1747 let schema = test_schema();
1748 let mut recordbatches = recordbatches_from_flight_message_stream(
1749 "test-peer".to_string(),
1750 stream::iter(vec![
1751 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1752 Ok(FlightMessage::Metrics(test_metrics_json())),
1753 Err(Error::from(Status::internal("boom after metrics"))),
1754 ]),
1755 )
1756 .await
1757 .unwrap();
1758
1759 let err = recordbatches.next().await.unwrap().unwrap_err();
1760 assert_eq!("External error", err.to_string());
1761 assert!(
1762 format!("{err:?}").contains("boom after metrics"),
1763 "unexpected error: {err:?}"
1764 );
1765 assert!(recordbatches.next().await.is_none());
1766 }
1767}