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::RegionResponse;
22use api::v1::ResponseHeader;
23use api::v1::region::{
24    RegionRequest, RegionRequestHeader, RemoteDynFilterRequest, RemoteDynFilterUnregister,
25    RemoteDynFilterUpdate, region_request, remote_dyn_filter_request,
26};
27use arc_swap::ArcSwapOption;
28use arrow_flight::Ticket;
29use async_stream::stream;
30use async_trait::async_trait;
31use common_error::ext::{BoxedError, ErrorExt};
32use common_error::status_code::StatusCode;
33use common_grpc::flight::{FlightDecoder, FlightMessage};
34use common_meta::error::{self as meta_error, Result as MetaResult};
35use common_meta::node_manager::Datanode;
36use common_query::request::QueryRequest;
37use common_recordbatch::error::ExternalSnafu;
38use common_recordbatch::{RecordBatch, RecordBatchStreamWrapper, SendableRecordBatchStream};
39use common_telemetry::error;
40use common_telemetry::tracing::Span;
41use common_telemetry::tracing_context::TracingContext;
42use futures_util::Stream;
43use prost::Message;
44use query::query_engine::DefaultSerializer;
45use snafu::{OptionExt, ResultExt, location};
46use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
47use tokio_stream::StreamExt;
48use tonic::codec::CompressionEncoding;
49
50use crate::error::{
51    self, FlightGetSnafu, IllegalDatabaseResponseSnafu, IllegalFlightMessagesSnafu,
52    MissingFieldSnafu, Result, ServerSnafu,
53};
54use crate::flight::{FlightMessageReader, decode_flight_data};
55use crate::region::codec::RegionCodec;
56use crate::{Client, metrics};
57
58const FLIGHT_DO_GET_TIMEOUT: Duration = Duration::from_secs(10);
59
60/// Sends region operations through a configured gRPC client.
61#[derive(Debug)]
62pub struct RegionRequester {
63    client: Client,
64    send_compression: bool,
65    accept_compression: bool,
66}
67
68#[async_trait]
69impl Datanode for RegionRequester {
70    async fn handle_request(&self, request: Arc<RegionRequest>) -> MetaResult<RegionResponse> {
71        self.handle_inner(request).await.map_err(Into::into)
72    }
73
74    async fn handle_query(&self, request: QueryRequest) -> MetaResult<SendableRecordBatchStream> {
75        let plan = DFLogicalSubstraitConvertor
76            .encode(&request.plan, DefaultSerializer)
77            .map_err(BoxedError::new)
78            .context(meta_error::ExternalSnafu)?
79            .to_vec();
80        let request = api::v1::region::QueryRequest {
81            header: request.header,
82            region_id: request.region_id.as_u64(),
83            plan,
84        };
85
86        let ticket = Ticket {
87            ticket: request.encode_to_vec().into(),
88        };
89        self.do_get_inner(ticket)
90            .await
91            .map_err(BoxedError::new)
92            .context(meta_error::ExternalSnafu)
93    }
94}
95
96impl RegionRequester {
97    pub fn new(client: Client, send_compression: bool, accept_compression: bool) -> Self {
98        Self {
99            client,
100            send_compression,
101            accept_compression,
102        }
103    }
104
105    pub async fn do_get_inner(&self, ticket: Ticket) -> Result<SendableRecordBatchStream> {
106        let mut flight_client = self
107            .client
108            .make_flight_client(self.send_compression, self.accept_compression)?;
109        // Limit Flight DoGet response time without limiting query stream execution.
110        let addr = flight_client.addr().to_string();
111        let mut request = tonic::Request::new(ticket);
112        request.set_timeout(FLIGHT_DO_GET_TIMEOUT);
113        let response = flight_client
114            .mut_inner()
115            .do_get(request)
116            .await
117            .or_else(|e| {
118                let tonic_code = e.code();
119                let e: error::Error = e.into();
120                error!(
121                    e; "Failed to do Flight get, addr: {}, code: {}",
122                    addr,
123                    tonic_code
124                );
125                Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
126                    addr: addr.clone(),
127                    tonic_code,
128                })
129            })?;
130
131        let flight_data_stream = response.into_inner();
132        let mut decoder = FlightDecoder::default();
133
134        let flight_message_stream = flight_data_stream
135            .filter_map(move |flight_data| decode_flight_data(&mut decoder, flight_data));
136
137        recordbatches_from_flight_message_stream(addr, flight_message_stream).await
138    }
139
140    /// Sends an owned or shared request without deep-cloning its protobuf payload.
141    ///
142    /// Accepts both [`RegionRequest`] and [`Arc<RegionRequest>`]. Callers can retain a
143    /// shared request for another attempt; this method does not retry or determine
144    /// whether replaying the request is safe.
145    pub async fn handle<R>(&self, request: R) -> Result<RegionResponse>
146    where
147        R: Borrow<RegionRequest> + Send + Sync + 'static,
148    {
149        self.handle_inner(request).await
150    }
151
152    /// Sends a remote dynamic filter update to the region service.
153    pub async fn handle_remote_dyn_filter_update(
154        &self,
155        query_id: impl Into<String>,
156        update: RemoteDynFilterUpdate,
157    ) -> Result<RegionResponse> {
158        self.handle_inner(build_remote_dyn_filter_update_request(query_id, update))
159            .await
160    }
161
162    /// Unregisters a remote dynamic filter in the region service.
163    pub async fn handle_remote_dyn_filter_unregister(
164        &self,
165        query_id: impl Into<String>,
166        unregister: RemoteDynFilterUnregister,
167    ) -> Result<RegionResponse> {
168        self.handle_inner(build_remote_dyn_filter_unregister_request(
169            query_id, unregister,
170        ))
171        .await
172    }
173
174    async fn handle_inner<R>(&self, request: R) -> Result<RegionResponse>
175    where
176        R: Borrow<RegionRequest> + Send + Sync + 'static,
177    {
178        let request_body = request
179            .borrow()
180            .body
181            .as_ref()
182            .context(MissingFieldSnafu { field: "body" })?;
183        let is_insert = matches!(
184            request_body,
185            region_request::Body::Inserts(_) | region_request::Body::BulkInsert(_)
186        );
187        let request_type = request_body.as_ref().to_string();
188        let _timer = metrics::METRIC_REGION_REQUEST_GRPC
189            .with_label_values(&[request_type.as_str()])
190            .start_timer();
191
192        let (addr, mut client) = self.client.raw_region_client()?;
193        if is_insert {
194            if self.send_compression {
195                client = client.send_compressed(CompressionEncoding::Zstd);
196            }
197            if self.accept_compression {
198                client = client.accept_compressed(CompressionEncoding::Zstd);
199            }
200        }
201
202        let response = async {
203            client
204                .ready()
205                .await
206                .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {e}")))?;
207            let mut request = tonic::Request::new(request);
208            request.extensions_mut().insert(tonic::GrpcMethod::new(
209                "greptime.v1.region.Region",
210                "Handle",
211            ));
212            client
213                .unary(
214                    request,
215                    tonic::codegen::http::uri::PathAndQuery::from_static(
216                        "/greptime.v1.region.Region/Handle",
217                    ),
218                    RegionCodec::default(),
219                )
220                .await
221        }
222        .await
223        .map_err(|status| map_region_error(status, addr))?
224        .into_inner();
225
226        check_response_header(&response.header)?;
227
228        Ok(RegionResponse::from_region_response(response))
229    }
230}
231
232fn map_region_error(status: tonic::Status, addr: String) -> error::Error {
233    error::Error::RegionServer {
234        addr,
235        code: status.code(),
236        source: BoxedError::new(error::Error::from(status)),
237        location: location!(),
238    }
239}
240
241async fn recordbatches_from_flight_message_stream<S>(
242    addr: String,
243    flight_message_stream: S,
244) -> Result<SendableRecordBatchStream>
245where
246    S: Stream<Item = Result<FlightMessage>> + Send + Unpin + 'static,
247{
248    let mut reader = FlightMessageReader::new(addr.clone(), flight_message_stream);
249    let FlightMessage::Schema(schema) = reader
250        .read_first()
251        .await
252        .map_err(|error| flight_stream_error(reader.remote_addr(), error))?
253    else {
254        return IllegalFlightMessagesSnafu {
255            reason: "Expect schema to be the first flight message",
256        }
257        .fail()
258        .map_err(|error| flight_stream_error(reader.remote_addr(), error));
259    };
260
261    let metrics = Arc::new(ArcSwapOption::from(None));
262    let metrics_ref = metrics.clone();
263
264    let tracing_context = TracingContext::from_current_span();
265
266    let schema =
267        Arc::new(datatypes::schema::Schema::try_from(schema).context(error::ConvertSchemaSnafu)?);
268    let schema_cloned = schema.clone();
269    let stream_addr = addr;
270    let stream = Box::pin(stream!({
271        let _span = tracing_context.attach(common_telemetry::tracing::info_span!(
272            "poll_flight_data_stream"
273        ));
274
275        loop {
276            let flight_message = match reader.read_next().await {
277                Ok(Some(message)) => message,
278                Ok(None) => break,
279                Err(error) => {
280                    yield Err(BoxedError::new(flight_stream_error(&stream_addr, error)))
281                        .context(ExternalSnafu);
282                    break;
283                }
284            };
285
286            match flight_message {
287                FlightMessage::RecordBatch(record_batch) => {
288                    // Deliver each batch immediately. In particular, do not
289                    // wait for a possible following Metrics message; it is
290                    // consumed on the next poll of this stream.
291                    yield Ok(RecordBatch::from_df_record_batch(
292                        schema_cloned.clone(),
293                        record_batch,
294                    ));
295                }
296                FlightMessage::Metrics(s) => {
297                    // Metrics may arrive before the next RecordBatch.
298                    match serde_json::from_str(&s) {
299                        Ok(metrics) => {
300                            metrics_ref.swap(Some(Arc::new(metrics)));
301                        }
302                        Err(error) => {
303                            common_telemetry::warn!(
304                                "Failed to decode region Flight metrics: {}",
305                                error
306                            );
307                        }
308                    }
309                    continue;
310                }
311                _ => {
312                    yield IllegalFlightMessagesSnafu {
313                        reason: "A Schema message must be succeeded exclusively by a set of RecordBatch messages"
314                    }
315                    .fail()
316                    .map_err(BoxedError::new)
317                    .context(ExternalSnafu);
318                    break;
319                }
320            }
321        }
322    }));
323    let record_batch_stream = RecordBatchStreamWrapper {
324        schema,
325        stream,
326        output_ordering: None,
327        metrics,
328        span: Span::current(),
329    };
330    Ok(Box::pin(record_batch_stream))
331}
332
333fn flight_stream_error(addr: &str, error: error::Error) -> error::Error {
334    let tonic_code = error.tonic_code().unwrap_or(tonic::Code::Unknown);
335    if error.status_code().should_log_error() {
336        error!(
337            error; "Failed to receive Flight data, addr: {}, code: {}",
338            addr,
339            tonic_code
340        );
341    }
342
343    error::Error::FlightGet {
344        addr: addr.to_string(),
345        tonic_code,
346        source: BoxedError::new(error),
347    }
348}
349
350pub fn build_remote_dyn_filter_update_request(
351    query_id: impl Into<String>,
352    update: RemoteDynFilterUpdate,
353) -> RegionRequest {
354    build_remote_dyn_filter_request(
355        query_id.into(),
356        remote_dyn_filter_request::Action::Update(update),
357    )
358}
359
360pub fn build_remote_dyn_filter_unregister_request(
361    query_id: impl Into<String>,
362    unregister: RemoteDynFilterUnregister,
363) -> RegionRequest {
364    build_remote_dyn_filter_request(
365        query_id.into(),
366        remote_dyn_filter_request::Action::Unregister(unregister),
367    )
368}
369
370fn build_remote_dyn_filter_request(
371    query_id: String,
372    action: remote_dyn_filter_request::Action,
373) -> RegionRequest {
374    RegionRequest {
375        header: Some(RegionRequestHeader {
376            tracing_context: TracingContext::from_current_span().to_w3c(),
377            ..Default::default()
378        }),
379        body: Some(region_request::Body::RemoteDynFilter(
380            RemoteDynFilterRequest {
381                query_id,
382                action: Some(action),
383            },
384        )),
385    }
386}
387
388pub fn check_response_header(header: &Option<ResponseHeader>) -> Result<()> {
389    let status = header
390        .as_ref()
391        .and_then(|header| header.status.as_ref())
392        .context(IllegalDatabaseResponseSnafu {
393            err_msg: "either response header or status is missing",
394        })?;
395
396    if StatusCode::is_success(status.status_code) {
397        Ok(())
398    } else {
399        let code =
400            StatusCode::from_u32(status.status_code).context(IllegalDatabaseResponseSnafu {
401                err_msg: format!("unknown server status: {:?}", status),
402            })?;
403        ServerSnafu {
404            code,
405            msg: status.err_msg.clone(),
406        }
407        .fail()
408    }
409}
410
411#[cfg(test)]
412#[allow(deprecated)]
413mod tests {
414    use std::collections::VecDeque;
415    use std::sync::Mutex;
416
417    use api::v1::region::region_server::{Region, RegionServer};
418    use api::v1::region::{
419        BulkInsertRequest, InsertRequest, InsertRequests, RegionResponse as PbRegionResponse,
420        RemoteDynFilterUnregister, RemoteDynFilterUpdate, region_request,
421        remote_dyn_filter_request,
422    };
423    use api::v1::value::ValueData;
424    use api::v1::{
425        ColumnDataType, ColumnSchema as PbColumnSchema, Row, Rows, Status as PbStatus, Value,
426    };
427    use common_error::ext::RetryHint;
428    use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
429    use common_recordbatch::adapter::RecordBatchMetrics;
430    use datatypes::arrow::array::Int32Array;
431    use datatypes::prelude::{ConcreteDataType, VectorRef};
432    use datatypes::schema::{ColumnSchema, Schema};
433    use datatypes::vectors::Int32Vector;
434    use futures_util::stream;
435    use tokio::net::TcpListener;
436    use tokio::task::JoinHandle;
437    use tokio_stream::wrappers::TcpListenerStream;
438    use tonic::codec::CompressionEncoding;
439    use tonic::{Request, Response, Status};
440
441    use super::*;
442    use crate::Error;
443
444    #[derive(Clone, Default)]
445    struct RecordingRegionService {
446        requests: Arc<Mutex<Vec<(RegionRequest, bool, bool)>>>,
447        responses: Arc<Mutex<VecDeque<std::result::Result<PbRegionResponse, Status>>>>,
448    }
449
450    #[tonic::async_trait]
451    impl Region for RecordingRegionService {
452        async fn handle(
453            &self,
454            request: Request<RegionRequest>,
455        ) -> std::result::Result<Response<PbRegionResponse>, Status> {
456            let metadata = request.metadata();
457            let sends_zstd = metadata.get("grpc-encoding").is_some_and(|v| v == "zstd");
458            let accepts_zstd = metadata
459                .get("grpc-accept-encoding")
460                .and_then(|v| v.to_str().ok())
461                .is_some_and(|v| v.split(',').any(|encoding| encoding == "zstd"));
462            self.requests
463                .lock()
464                .unwrap()
465                .push((request.into_inner(), sends_zstd, accepts_zstd));
466            self.responses
467                .lock()
468                .unwrap()
469                .pop_front()
470                .unwrap_or_else(|| Ok(build_response(StatusCode::Success)))
471                .map(Response::new)
472        }
473    }
474
475    struct RegionTestServer {
476        addr: String,
477        service: RecordingRegionService,
478        task: JoinHandle<()>,
479    }
480
481    impl RegionTestServer {
482        async fn start() -> Self {
483            let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
484            let addr = listener.local_addr().unwrap().to_string();
485            let service = RecordingRegionService::default();
486            let server_service = service.clone();
487            let task = tokio::spawn(async move {
488                tonic::transport::Server::builder()
489                    .add_service(
490                        RegionServer::new(server_service)
491                            .accept_compressed(CompressionEncoding::Zstd)
492                            .send_compressed(CompressionEncoding::Zstd),
493                    )
494                    .serve_with_incoming(TcpListenerStream::new(listener))
495                    .await
496                    .unwrap();
497            });
498            Self {
499                addr,
500                service,
501                task,
502            }
503        }
504
505        fn create_requester(
506            &self,
507            config: ChannelConfig,
508            send: bool,
509            accept: bool,
510        ) -> RegionRequester {
511            RegionRequester::new(
512                Client::with_manager_and_urls(
513                    ChannelManager::with_config(config, None),
514                    [self.addr.clone()],
515                ),
516                send,
517                accept,
518            )
519        }
520    }
521
522    impl Drop for RegionTestServer {
523        fn drop(&mut self) {
524            self.task.abort();
525        }
526    }
527
528    fn build_response(code: StatusCode) -> PbRegionResponse {
529        PbRegionResponse {
530            header: Some(ResponseHeader {
531                status: Some(PbStatus {
532                    status_code: code as u32,
533                    ..Default::default()
534                }),
535            }),
536            affected_rows: 1,
537            ..Default::default()
538        }
539    }
540
541    fn build_insert_request() -> RegionRequest {
542        RegionRequest {
543            header: Some(RegionRequestHeader {
544                dbname: "shared-request-test".to_string(),
545                ..Default::default()
546            }),
547            body: Some(region_request::Body::Inserts(InsertRequests {
548                requests: vec![InsertRequest {
549                    region_id: 42,
550                    rows: Some(Rows {
551                        schema: vec![
552                            PbColumnSchema {
553                                column_name: "message".to_string(),
554                                datatype: ColumnDataType::String as i32,
555                                ..Default::default()
556                            },
557                            PbColumnSchema {
558                                column_name: "payload".to_string(),
559                                datatype: ColumnDataType::Binary as i32,
560                                ..Default::default()
561                            },
562                        ],
563                        rows: vec![Row {
564                            values: vec![
565                                Value {
566                                    value_data: Some(ValueData::StringValue(
567                                        "hello 世界".to_string(),
568                                    )),
569                                },
570                                Value {
571                                    value_data: Some(ValueData::BinaryValue(vec![0, 1, 127, 255])),
572                                },
573                            ],
574                        }],
575                    }),
576                    ..Default::default()
577                }],
578            })),
579        }
580    }
581
582    #[tokio::test]
583    async fn test_owned_and_shared_requests_preserve_payload_and_compression() {
584        let server = RegionTestServer::start().await;
585        let shared = Arc::new(build_insert_request());
586        for (send, accept) in [(false, false), (true, false), (false, true), (true, true)] {
587            let requester = server.create_requester(ChannelConfig::default(), send, accept);
588            let channel =
589                tonic::transport::Endpoint::from_shared(format!("http://{}", server.addr))
590                    .unwrap()
591                    .connect()
592                    .await
593                    .unwrap();
594            let mut baseline = api::v1::region::region_client::RegionClient::new(channel);
595            if send {
596                baseline = baseline.send_compressed(CompressionEncoding::Zstd);
597            }
598            if accept {
599                baseline = baseline.accept_compressed(CompressionEncoding::Zstd);
600            }
601            assert_eq!(
602                baseline
603                    .handle(build_insert_request())
604                    .await
605                    .unwrap()
606                    .into_inner()
607                    .affected_rows,
608                1
609            );
610            assert_eq!(
611                requester
612                    .handle(build_insert_request())
613                    .await
614                    .unwrap()
615                    .affected_rows,
616                1
617            );
618            assert_eq!(
619                requester
620                    .handle(Arc::clone(&shared))
621                    .await
622                    .unwrap()
623                    .affected_rows,
624                1
625            );
626        }
627        let requests = server.service.requests.lock().unwrap();
628        assert_eq!(requests.len(), 12);
629        for ((request, sends, accepts), (send, accept)) in requests.iter().zip(
630            [(false, false), (true, false), (false, true), (true, true)]
631                .into_iter()
632                .flat_map(|settings| [settings, settings, settings]),
633        ) {
634            assert_eq!(request, shared.as_ref());
635            assert_eq!((*sends, *accepts), (send, accept));
636        }
637    }
638
639    #[tokio::test]
640    async fn test_shared_request_can_be_resent_after_transport_error() {
641        let server = RegionTestServer::start().await;
642        let mut status = Status::unavailable("migrating");
643        status.metadata_mut().insert(
644            common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
645            RetryHint::Retryable.as_str().parse().unwrap(),
646        );
647        server
648            .service
649            .responses
650            .lock()
651            .unwrap()
652            .push_back(Err(status));
653        let requester = server.create_requester(ChannelConfig::default(), false, false);
654        let shared = Arc::new(build_insert_request());
655        let error = requester.handle(Arc::clone(&shared)).await.unwrap_err();
656        assert_eq!(error.retry_hint(), RetryHint::Retryable);
657        assert!(
658            matches!(error, Error::RegionServer { addr, code: tonic::Code::Unavailable, .. } if addr == server.addr)
659        );
660        requester.handle(Arc::clone(&shared)).await.unwrap();
661        let requests = server.service.requests.lock().unwrap();
662        assert_eq!(requests.len(), 2);
663        assert_eq!(&requests[0].0, shared.as_ref());
664        assert_eq!(&requests[1].0, shared.as_ref());
665    }
666
667    #[test]
668    fn test_retry_hint_wire_compatibility() {
669        for (name, wire_hint, expected_hint) in [
670            ("legacy_missing", None, RetryHint::NonRetryable),
671            ("legacy_retryable", Some("retryable"), RetryHint::Retryable),
672            (
673                "legacy_non_retryable",
674                Some("non_retryable"),
675                RetryHint::NonRetryable,
676            ),
677            (
678                "route_refresh",
679                Some("retry_after_route_refresh"),
680                RetryHint::RetryAfterRouteRefresh,
681            ),
682            (
683                "unknown_future_hint",
684                Some("future_retry_hint"),
685                RetryHint::NonRetryable,
686            ),
687        ] {
688            let mut status = Status::failed_precondition("region unavailable");
689            status.metadata_mut().insert(
690                common_error::GREPTIME_DB_HEADER_ERROR_CODE,
691                (StatusCode::RegionNotReady as u32)
692                    .to_string()
693                    .parse()
694                    .unwrap(),
695            );
696            if let Some(hint) = wire_hint {
697                status.metadata_mut().insert(
698                    common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
699                    hint.parse().unwrap(),
700                );
701            }
702            let error: Error = status.into();
703            assert_eq!(error.retry_hint(), expected_hint, "{name}");
704            assert_eq!(error.status_code(), StatusCode::RegionNotReady, "{name}");
705            assert_eq!(
706                error.tonic_code(),
707                Some(tonic::Code::FailedPrecondition),
708                "{name}"
709            );
710        }
711    }
712
713    #[tokio::test]
714    async fn test_transport_error_parity_and_datanode_mapping() {
715        let server = RegionTestServer::start().await;
716        let requester = server.create_requester(ChannelConfig::default(), false, false);
717        for hint in [
718            RetryHint::Retryable,
719            RetryHint::RetryAfterRouteRefresh,
720            RetryHint::NonRetryable,
721        ] {
722            let mut status = Status::failed_precondition("region unavailable");
723            status.metadata_mut().insert(
724                common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
725                hint.as_str().parse().unwrap(),
726            );
727            status.metadata_mut().insert(
728                common_error::GREPTIME_DB_HEADER_ERROR_CODE,
729                (StatusCode::RegionNotFound as u32)
730                    .to_string()
731                    .parse()
732                    .unwrap(),
733            );
734            server
735                .service
736                .responses
737                .lock()
738                .unwrap()
739                .extend((0..4).map(|_| Err(status.clone())));
740            let mut baseline = api::v1::region::region_client::RegionClient::connect(format!(
741                "http://{}",
742                server.addr
743            ))
744            .await
745            .unwrap();
746            let baseline: Error = baseline
747                .handle(build_insert_request())
748                .await
749                .unwrap_err()
750                .into();
751            assert_eq!(baseline.status_code(), StatusCode::RegionNotFound);
752            assert_eq!(baseline.retry_hint(), hint);
753            assert_eq!(baseline.tonic_code(), Some(tonic::Code::FailedPrecondition));
754            let owned = requester.handle(build_insert_request()).await.unwrap_err();
755            let shared = requester
756                .handle(Arc::new(build_insert_request()))
757                .await
758                .unwrap_err();
759            for error in [owned, shared] {
760                assert_eq!(error.status_code(), baseline.status_code());
761                assert_eq!(error.retry_hint(), baseline.retry_hint());
762                assert_eq!(error.tonic_code(), baseline.tonic_code());
763            }
764            let datanode: &dyn Datanode = &requester;
765            let error = datanode.handle(build_insert_request()).await.unwrap_err();
766            assert_eq!(error.status_code(), StatusCode::RegionNotFound);
767            assert_eq!(error.retry_hint(), hint);
768            assert!(matches!(error, meta_error::Error::External { .. }));
769        }
770    }
771
772    #[tokio::test]
773    async fn test_datanode_transport_retry_hints() {
774        let server = RegionTestServer::start().await;
775        let requester = server.create_requester(ChannelConfig::default(), false, false);
776        let datanode: &dyn Datanode = &requester;
777        let statuses = [
778            tonic::Code::Unavailable,
779            tonic::Code::Cancelled,
780            tonic::Code::DeadlineExceeded,
781            tonic::Code::FailedPrecondition,
782        ]
783        .into_iter()
784        .flat_map(|code| {
785            [
786                None,
787                Some(RetryHint::NonRetryable),
788                Some(RetryHint::Retryable),
789                Some(RetryHint::RetryAfterRouteRefresh),
790            ]
791            .into_iter()
792            .map(move |hint| (code, hint))
793        });
794        for (code, hint) in statuses {
795            for body in [
796                region_request::Body::Inserts(Default::default()),
797                region_request::Body::Deletes(Default::default()),
798                region_request::Body::BulkInsert(Default::default()),
799                region_request::Body::Create(Default::default()),
800                region_request::Body::RemoteDynFilter(Default::default()),
801            ] {
802                let request_type = body.as_ref().to_string();
803                let mut status = Status::new(code, "request failed");
804                // Metadata status is deliberately different from the transport status.
805                status.metadata_mut().insert(
806                    common_error::GREPTIME_DB_HEADER_ERROR_CODE,
807                    (StatusCode::RegionNotReady as u32)
808                        .to_string()
809                        .parse()
810                        .unwrap(),
811                );
812                if let Some(hint) = hint {
813                    status.metadata_mut().insert(
814                        common_error::GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
815                        hint.as_str().parse().unwrap(),
816                    );
817                }
818                let original = Error::from(status.clone());
819                let expected = if original.should_retry() && !original.retry_hint().is_retryable() {
820                    RetryHint::Retryable
821                } else {
822                    original.retry_hint()
823                };
824                server
825                    .service
826                    .responses
827                    .lock()
828                    .unwrap()
829                    .extend([Err(status.clone()), Err(status)]);
830                let request = RegionRequest {
831                    header: None,
832                    body: Some(body),
833                };
834                let shared = Arc::new(request);
835                let shared_error = datanode.handle(Arc::clone(&shared)).await.unwrap_err();
836                assert_eq!(Arc::strong_count(&shared), 1);
837                let request = Arc::try_unwrap(shared).unwrap();
838                let owned_error = datanode.handle(request).await.unwrap_err();
839                for error in [owned_error, shared_error] {
840                    assert_eq!(
841                        error.retry_hint(),
842                        expected,
843                        "{code:?}, {hint:?}, {request_type}"
844                    );
845                    assert_eq!(error.retry_hint().is_retryable(), original.should_retry());
846                    assert_eq!(error.status_code(), StatusCode::RegionNotReady);
847                    let meta_error::Error::External { source, .. } = error else {
848                        panic!("expected an external error");
849                    };
850                    assert_eq!(source.retry_hint(), expected);
851                    let Error::RegionServer {
852                        source,
853                        code: actual_code,
854                        ..
855                    } = source.as_any().downcast_ref::<Error>().unwrap()
856                    else {
857                        panic!("expected a region server error");
858                    };
859                    assert_eq!(*actual_code, code);
860                    assert_eq!(source.retry_hint(), original.retry_hint());
861                    assert_eq!(source.to_string(), original.to_string());
862                }
863            }
864        }
865    }
866
867    #[tokio::test]
868    async fn test_shared_request_preserves_response_header_errors() {
869        let server = RegionTestServer::start().await;
870        server.service.responses.lock().unwrap().extend([
871            Ok(PbRegionResponse::default()),
872            Ok(build_response(StatusCode::RegionNotFound)),
873        ]);
874        let requester = server.create_requester(ChannelConfig::default(), false, false);
875        let shared = Arc::new(build_insert_request());
876        assert!(matches!(
877            requester.handle(Arc::clone(&shared)).await.unwrap_err(),
878            Error::IllegalDatabaseResponse { .. }
879        ));
880        assert!(matches!(
881            requester.handle(shared).await.unwrap_err(),
882            Error::Server {
883                code: StatusCode::RegionNotFound,
884                ..
885            }
886        ));
887    }
888
889    #[tokio::test]
890    async fn test_shared_request_respects_configured_message_limits() {
891        let server = RegionTestServer::start().await;
892        let shared = Arc::new(build_insert_request());
893        for send_limit in [true, false] {
894            let mut config = ChannelConfig::default();
895            if send_limit {
896                config.max_send_message_size = "1B".parse().unwrap();
897            } else {
898                config.max_recv_message_size = "1B".parse().unwrap();
899            }
900            let mut baseline = api::v1::region::region_client::RegionClient::connect(format!(
901                "http://{}",
902                server.addr
903            ))
904            .await
905            .unwrap()
906            .max_encoding_message_size(config.max_send_message_size.as_bytes() as usize)
907            .max_decoding_message_size(config.max_recv_message_size.as_bytes() as usize);
908            let baseline: Error = baseline
909                .handle(build_insert_request())
910                .await
911                .unwrap_err()
912                .into();
913            if !send_limit {
914                assert_eq!(
915                    baseline.tonic_code(),
916                    Some(tonic::Code::OutOfRange),
917                    "{baseline:?}"
918                );
919            }
920            let requester = server.create_requester(config, false, false);
921            let error = requester.handle(Arc::clone(&shared)).await.unwrap_err();
922            assert_eq!(
923                error.tonic_code(),
924                baseline.tonic_code(),
925                "shared: {error:?}; generated: {baseline:?}"
926            );
927            assert_eq!(
928                error.status_code(),
929                baseline.status_code(),
930                "shared: {error:?}; generated: {baseline:?}"
931            );
932            let Error::RegionServer { source, .. } = &error else {
933                panic!("expected a region server error: {error:?}");
934            };
935            assert_eq!(
936                source.retry_hint(),
937                baseline.retry_hint(),
938                "shared: {error:?}; generated: {baseline:?}"
939            );
940            assert_eq!(
941                server.service.requests.lock().unwrap().len(),
942                if send_limit { 0 } else { 2 }
943            );
944        }
945        let requester = server.create_requester(ChannelConfig::default(), false, false);
946        requester.handle(shared).await.unwrap();
947    }
948
949    #[tokio::test]
950    async fn test_inserts_and_bulk_insert_use_transport_compression() {
951        let server = RegionTestServer::start().await;
952        let requester = server.create_requester(ChannelConfig::default(), true, true);
953        let send_only_requester = server.create_requester(ChannelConfig::default(), true, false);
954        let accept_only_requester = server.create_requester(ChannelConfig::default(), false, true);
955        let disabled_requester = server.create_requester(ChannelConfig::default(), false, false);
956        let inserts = || RegionRequest {
957            body: Some(region_request::Body::Inserts(Default::default())),
958            ..Default::default()
959        };
960        let bulk_insert = || RegionRequest {
961            body: Some(region_request::Body::BulkInsert(
962                BulkInsertRequest::default(),
963            )),
964            ..Default::default()
965        };
966
967        requester.handle(inserts()).await.unwrap();
968        send_only_requester.handle(inserts()).await.unwrap();
969        accept_only_requester.handle(inserts()).await.unwrap();
970        requester.handle(bulk_insert()).await.unwrap();
971        disabled_requester.handle(bulk_insert()).await.unwrap();
972        requester
973            .handle(build_remote_dyn_filter_unregister_request(
974                "query-1",
975                RemoteDynFilterUnregister {
976                    filter_id: "filter-1".to_string(),
977                },
978            ))
979            .await
980            .unwrap();
981
982        assert_eq!(
983            vec![
984                (true, true),
985                (true, false),
986                (false, true),
987                (true, true),
988                (false, false),
989                (false, false)
990            ],
991            server
992                .service
993                .requests
994                .lock()
995                .unwrap()
996                .iter()
997                .map(|(_, send, accept)| (*send, *accept))
998                .collect::<Vec<_>>()
999        );
1000    }
1001
1002    #[test]
1003    fn test_flight_stream_error_preserves_peer_address() {
1004        let error = flight_stream_error(
1005            "127.0.0.1:4001",
1006            tonic::Status::unavailable("datanode unavailable").into(),
1007        );
1008
1009        assert!(matches!(
1010            error,
1011            error::Error::FlightGet {
1012                addr,
1013                tonic_code: tonic::Code::Unavailable,
1014                ..
1015            } if addr == "127.0.0.1:4001"
1016        ));
1017    }
1018
1019    #[tokio::test]
1020    async fn test_empty_flight_stream_preserves_peer_address() {
1021        let Err(error) = recordbatches_from_flight_message_stream(
1022            "127.0.0.1:4001".to_string(),
1023            stream::empty::<Result<FlightMessage>>(),
1024        )
1025        .await
1026        else {
1027            panic!("expected empty Flight stream to fail");
1028        };
1029
1030        assert!(matches!(
1031            error,
1032            error::Error::FlightGet {
1033                addr,
1034                tonic_code: tonic::Code::Unknown,
1035                ..
1036            } if addr == "127.0.0.1:4001"
1037        ));
1038    }
1039
1040    fn test_schema() -> Arc<Schema> {
1041        Arc::new(Schema::new(vec![ColumnSchema::new(
1042            "v",
1043            ConcreteDataType::int32_datatype(),
1044            false,
1045        )]))
1046    }
1047
1048    fn test_metrics_json() -> String {
1049        serde_json::to_string(&RecordBatchMetrics {
1050            elapsed_compute: 7,
1051            ..Default::default()
1052        })
1053        .unwrap()
1054    }
1055
1056    #[test]
1057    fn test_check_response_header() {
1058        let result = check_response_header(&None);
1059        assert!(matches!(
1060            result.unwrap_err(),
1061            Error::IllegalDatabaseResponse { .. }
1062        ));
1063
1064        let result = check_response_header(&Some(ResponseHeader { status: None }));
1065        assert!(matches!(
1066            result.unwrap_err(),
1067            Error::IllegalDatabaseResponse { .. }
1068        ));
1069
1070        let result = check_response_header(&Some(ResponseHeader {
1071            status: Some(PbStatus {
1072                status_code: StatusCode::Success as u32,
1073                err_msg: String::default(),
1074            }),
1075        }));
1076        assert!(result.is_ok());
1077
1078        let result = check_response_header(&Some(ResponseHeader {
1079            status: Some(PbStatus {
1080                status_code: u32::MAX,
1081                err_msg: String::default(),
1082            }),
1083        }));
1084        assert!(matches!(
1085            result.unwrap_err(),
1086            Error::IllegalDatabaseResponse { .. }
1087        ));
1088
1089        let result = check_response_header(&Some(ResponseHeader {
1090            status: Some(PbStatus {
1091                status_code: StatusCode::Internal as u32,
1092                err_msg: "blabla".to_string(),
1093            }),
1094        }));
1095        let Error::Server { code, msg, .. } = result.unwrap_err() else {
1096            unreachable!()
1097        };
1098        assert_eq!(code, StatusCode::Internal);
1099        assert_eq!(msg, "blabla");
1100    }
1101
1102    #[test]
1103    fn test_build_remote_dyn_filter_request_sets_header_and_body() {
1104        let request = build_remote_dyn_filter_update_request(
1105            "query-1",
1106            RemoteDynFilterUpdate {
1107                filter_id: "filter-1".to_string(),
1108                payload: vec![1, 2, 3],
1109                generation: 7,
1110                is_complete: false,
1111            },
1112        );
1113
1114        request.header.expect("remote dyn filter header must exist");
1115
1116        let body = request.body.expect("remote dyn filter body must exist");
1117        let region_request::Body::RemoteDynFilter(remote_request) = body else {
1118            panic!("expected remote dyn filter request body");
1119        };
1120
1121        assert_eq!(remote_request.query_id, "query-1");
1122        assert!(matches!(
1123            remote_request.action,
1124            Some(remote_dyn_filter_request::Action::Update(_))
1125        ));
1126    }
1127
1128    #[test]
1129    fn test_build_remote_dyn_filter_unregister_request_sets_header_and_body() {
1130        let request = build_remote_dyn_filter_unregister_request(
1131            "query-1",
1132            RemoteDynFilterUnregister {
1133                filter_id: "filter-9".to_string(),
1134            },
1135        );
1136
1137        request.header.expect("remote dyn filter header must exist");
1138
1139        let body = request.body.expect("remote dyn filter body must exist");
1140        let region_request::Body::RemoteDynFilter(remote_request) = body else {
1141            panic!("expected remote dyn filter request body");
1142        };
1143
1144        assert_eq!(remote_request.query_id, "query-1");
1145        assert!(matches!(
1146            remote_request.action,
1147            Some(remote_dyn_filter_request::Action::Unregister(_))
1148        ));
1149    }
1150
1151    #[tokio::test]
1152    async fn test_record_batch_stream_continues_after_pre_batch_metrics() {
1153        let schema = test_schema();
1154        let batch = RecordBatch::new(
1155            schema.clone(),
1156            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1157        )
1158        .unwrap();
1159
1160        let mut recordbatches = recordbatches_from_flight_message_stream(
1161            "test-peer".to_string(),
1162            stream::iter(vec![
1163                Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1164                Ok(FlightMessage::Metrics(test_metrics_json())),
1165                Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1166            ]),
1167        )
1168        .await
1169        .unwrap();
1170
1171        let batch = recordbatches.next().await.unwrap().unwrap();
1172        assert_eq!(batch.num_rows(), 1);
1173        assert!(recordbatches.next().await.is_none());
1174
1175        let metrics = recordbatches.metrics().unwrap();
1176        assert_eq!(metrics.elapsed_compute, 7);
1177    }
1178
1179    #[tokio::test]
1180    async fn test_record_batch_is_yielded_without_waiting_for_next_message() {
1181        let schema = test_schema();
1182        let batch = RecordBatch::new(
1183            schema.clone(),
1184            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1185        )
1186        .unwrap();
1187
1188        let messages = stream::iter(vec![
1189            Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1190            Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1191        ])
1192        .chain(stream::pending::<Result<FlightMessage>>());
1193        let mut recordbatches =
1194            recordbatches_from_flight_message_stream("test-peer".to_string(), messages)
1195                .await
1196                .unwrap();
1197
1198        let batch = tokio::time::timeout(Duration::from_secs(1), recordbatches.next())
1199            .await
1200            .expect("the first batch must not wait for lookahead")
1201            .unwrap()
1202            .unwrap();
1203        assert_eq!(batch.num_rows(), 1);
1204    }
1205
1206    #[tokio::test]
1207    async fn test_malformed_region_metrics_are_non_fatal() {
1208        let schema = test_schema();
1209        let batch = RecordBatch::new(
1210            schema.clone(),
1211            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1212        )
1213        .unwrap();
1214        let mut recordbatches = recordbatches_from_flight_message_stream(
1215            "test-peer".to_string(),
1216            stream::iter(vec![
1217                Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1218                Ok(FlightMessage::Metrics("{not-json}".to_string())),
1219                Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1220            ]),
1221        )
1222        .await
1223        .unwrap();
1224
1225        assert_eq!(recordbatches.next().await.unwrap().unwrap().num_rows(), 1);
1226        assert!(recordbatches.next().await.is_none());
1227    }
1228
1229    #[tokio::test]
1230    async fn test_record_batch_stream_preserves_following_record_batch() {
1231        let schema = test_schema();
1232        let first_batch = RecordBatch::new(
1233            schema.clone(),
1234            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1235        )
1236        .unwrap();
1237        let second_batch = RecordBatch::new(
1238            schema.clone(),
1239            vec![Arc::new(Int32Vector::from_slice([2])) as VectorRef],
1240        )
1241        .unwrap();
1242
1243        let mut recordbatches = recordbatches_from_flight_message_stream(
1244            "test-peer".to_string(),
1245            stream::iter(vec![
1246                Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1247                Ok(FlightMessage::RecordBatch(
1248                    first_batch.into_df_record_batch(),
1249                )),
1250                Ok(FlightMessage::RecordBatch(
1251                    second_batch.into_df_record_batch(),
1252                )),
1253            ]),
1254        )
1255        .await
1256        .unwrap();
1257
1258        let first_batch = recordbatches.next().await.unwrap().unwrap();
1259        let second_batch = recordbatches.next().await.unwrap().unwrap();
1260        assert_eq!(
1261            first_batch
1262                .column(0)
1263                .as_any()
1264                .downcast_ref::<Int32Array>()
1265                .unwrap()
1266                .value(0),
1267            1
1268        );
1269        assert_eq!(
1270            second_batch
1271                .column(0)
1272                .as_any()
1273                .downcast_ref::<Int32Array>()
1274                .unwrap()
1275                .value(0),
1276            2
1277        );
1278        assert!(recordbatches.next().await.is_none());
1279    }
1280
1281    #[tokio::test]
1282    async fn test_record_batch_stream_captures_final_metrics_after_record_batch() {
1283        let schema = test_schema();
1284        let batch = RecordBatch::new(
1285            schema.clone(),
1286            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1287        )
1288        .unwrap();
1289        let final_metrics = serde_json::to_string(&RecordBatchMetrics {
1290            elapsed_compute: 99,
1291            ..Default::default()
1292        })
1293        .unwrap();
1294        let mut recordbatches = recordbatches_from_flight_message_stream(
1295            "test-peer".to_string(),
1296            stream::iter(vec![
1297                Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1298                Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1299                Ok(FlightMessage::Metrics(final_metrics)),
1300            ]),
1301        )
1302        .await
1303        .unwrap();
1304
1305        assert_eq!(recordbatches.next().await.unwrap().unwrap().num_rows(), 1);
1306        assert!(recordbatches.next().await.is_none());
1307        assert_eq!(recordbatches.metrics().unwrap().elapsed_compute, 99);
1308    }
1309
1310    #[tokio::test]
1311    async fn test_record_batch_stream_exposes_error_after_pre_batch_metrics() {
1312        let schema = test_schema();
1313        let mut recordbatches = recordbatches_from_flight_message_stream(
1314            "test-peer".to_string(),
1315            stream::iter(vec![
1316                Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1317                Ok(FlightMessage::Metrics(test_metrics_json())),
1318                Err(Error::from(Status::internal("boom after metrics"))),
1319            ]),
1320        )
1321        .await
1322        .unwrap();
1323
1324        let err = recordbatches.next().await.unwrap().unwrap_err();
1325        assert_eq!("External error", err.to_string());
1326        assert!(
1327            format!("{err:?}").contains("boom after metrics"),
1328            "unexpected error: {err:?}"
1329        );
1330        assert!(recordbatches.next().await.is_none());
1331    }
1332}