Skip to main content

client/
region.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod 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/// Sends region operations through a configured gRPC client.
62#[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        // Limit Flight DoGet response time without limiting query stream execution.
111        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    /// Sends an owned or shared request without deep-cloning its protobuf payload.
142    ///
143    /// Accepts both [`RegionRequest`] and [`Arc<RegionRequest>`]. Callers can retain a
144    /// shared request for another attempt; this method does not retry or determine
145    /// whether replaying the request is safe. Callers accepting partial results in the
146    /// request header must inspect failed entries before retrying.
147    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    /// Sends a remote dynamic filter update to the region service.
155    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    /// Unregisters a remote dynamic filter in the region service.
165    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                    // Deliver each batch immediately. In particular, do not
304                    // wait for a possible following Metrics message; it is
305                    // consumed on the next poll of this stream.
306                    yield Ok(RecordBatch::from_df_record_batch(
307                        schema_cloned.clone(),
308                        record_batch,
309                    ));
310                }
311                FlightMessage::Metrics(s) => {
312                    // Metrics may arrive before the next RecordBatch.
313                    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
403/// Checks that the response contains a successful application status.
404pub 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
419/// Decodes an ordinary region response without per-entry write failures.
420pub 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
431/// Decodes an inserts/deletes response, validating failures against the original entry count.
432/// A failed single-entry request returns its underlying error.
433fn 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    // A single failed entry has no successful peers to preserve.
483    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                // Metadata status is deliberately different from the transport status.
958                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        // A partial envelope must not be interpreted as success by the header-only decoder.
1375        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}