Skip to main content

client/
database.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
15use std::pin::Pin;
16use std::str::FromStr;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{Arc, RwLock};
19use std::task::{Context, Poll};
20
21use api::v1::auth_header::AuthScheme;
22use api::v1::ddl_request::Expr as DdlExpr;
23use api::v1::greptime_database_client::GreptimeDatabaseClient;
24use api::v1::greptime_request::Request;
25use api::v1::query_request::Query;
26use api::v1::{
27    AlterTableExpr, AuthHeader, Basic, CreateTableExpr, DdlRequest, GreptimeRequest,
28    InsertRequests, QueryRequest, RequestHeader, RowInsertRequests,
29};
30use arc_swap::ArcSwapOption;
31use arrow_flight::{FlightData, Ticket};
32use async_stream::stream;
33use base64::Engine;
34use base64::prelude::BASE64_STANDARD;
35use common_catalog::build_db_string;
36use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
37use common_error::ext::BoxedError;
38use common_grpc::flight::do_put::DoPutResponse;
39use common_grpc::flight::{
40    FLOW_EXTENSIONS_METADATA_KEY, FlightDecoder, FlightMessage, SNAPSHOT_SEQS_METADATA_KEY,
41};
42use common_query::Output;
43use common_recordbatch::adapter::RecordBatchMetrics;
44use common_recordbatch::error::ExternalSnafu;
45use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream, RecordBatchStreamWrapper};
46use common_telemetry::tracing::Span;
47use common_telemetry::tracing_context::W3cTrace;
48use common_telemetry::{error, warn};
49use futures::future;
50use futures_util::{Stream, StreamExt, TryStreamExt};
51use prost::Message;
52use snafu::ResultExt;
53use tonic::metadata::{AsciiMetadataKey, AsciiMetadataValue, MetadataMap, MetadataValue};
54use tonic::transport::Channel;
55
56use crate::error::{
57    ConvertFlightDataSnafu, Error, FlightGetSnafu, IllegalFlightMessagesSnafu,
58    InvalidTonicMetadataValueSnafu,
59};
60use crate::flight::decode_flight_data;
61use crate::{Client, Result, error, from_grpc_response};
62
63type FlightDataStream = Pin<Box<dyn Stream<Item = FlightData> + Send>>;
64
65type DoPutResponseStream = Pin<Box<dyn Stream<Item = Result<DoPutResponse>>>>;
66
67/// Terminal metrics associated with a query output.
68///
69/// For streaming outputs, metrics are only final after the stream is fully
70/// drained and [`Self::is_ready`] returns `true`.
71#[derive(Debug, Clone, Default)]
72pub struct OutputMetrics {
73    inner: Arc<OutputMetricsInner>,
74}
75
76#[derive(Debug, Default)]
77struct OutputMetricsInner {
78    metrics: RwLock<Option<RecordBatchMetrics>>,
79    ready: AtomicBool,
80}
81
82impl OutputMetrics {
83    fn new() -> Self {
84        Self::default()
85    }
86
87    /// Replaces the current terminal metrics snapshot.
88    pub fn update(&self, metrics: Option<RecordBatchMetrics>) {
89        *self.inner.metrics.write().unwrap() = metrics;
90    }
91
92    /// Marks the terminal metrics as final for this output.
93    pub fn mark_ready(&self) {
94        let _ = self
95            .inner
96            .ready
97            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire);
98    }
99
100    /// Returns whether terminal metrics are final.
101    ///
102    /// Streaming outputs become ready only after the stream reaches EOF.
103    pub fn is_ready(&self) -> bool {
104        self.inner.ready.load(Ordering::Acquire)
105    }
106
107    /// Returns the latest terminal metrics snapshot, if any.
108    pub fn get(&self) -> Option<RecordBatchMetrics> {
109        self.inner.metrics.read().unwrap().clone()
110    }
111
112    /// Returns proved per-region watermarks.
113    ///
114    /// Entries whose watermark is `None` are intentionally omitted because they
115    /// represent participating regions whose terminal sequence bound was not
116    /// provable.
117    pub fn region_watermark_map(&self) -> Option<std::collections::HashMap<u64, u64>> {
118        Some(
119            self.get()?
120                .region_watermarks
121                .into_iter()
122                .filter_map(|entry| entry.watermark.map(|seq| (entry.region_id, seq)))
123                .collect::<std::collections::HashMap<_, _>>(),
124        )
125    }
126
127    /// Returns all regions that participated in terminal metric collection,
128    /// including entries whose watermark is `None`.
129    pub fn participating_regions(&self) -> Option<std::collections::BTreeSet<u64>> {
130        Some(
131            self.get()?
132                .region_watermarks
133                .into_iter()
134                .map(|entry| entry.region_id)
135                .collect::<std::collections::BTreeSet<_>>(),
136        )
137    }
138}
139
140/// Query output together with a handle for its terminal metrics.
141///
142/// The contained [`OutputMetrics`] lets callers read stream terminal metrics
143/// after consuming `output`. For non-stream outputs, metrics are ready
144/// immediately.
145#[derive(Debug)]
146pub struct OutputWithMetrics {
147    pub output: Output,
148    pub metrics: OutputMetrics,
149}
150
151impl OutputWithMetrics {
152    /// Wraps an output with a terminal metrics handle.
153    ///
154    /// Stream outputs update the handle as the stream is consumed. Non-stream
155    /// outputs are marked ready immediately.
156    pub fn from_output(output: Output) -> Self {
157        let terminal_metrics = OutputMetrics::new();
158        let output = attach_terminal_metrics(output, &terminal_metrics);
159        Self {
160            output,
161            metrics: terminal_metrics,
162        }
163    }
164
165    /// Returns proved per-region watermarks from the terminal metrics.
166    pub fn region_watermark_map(&self) -> Option<std::collections::HashMap<u64, u64>> {
167        self.metrics.region_watermark_map()
168    }
169
170    /// Returns all regions participating in terminal metric collection.
171    pub fn participating_regions(&self) -> Option<std::collections::BTreeSet<u64>> {
172        self.metrics.participating_regions()
173    }
174
175    /// Drops the terminal metrics handle and returns the original output.
176    pub fn into_output(self) -> Output {
177        self.output
178    }
179}
180
181fn parse_terminal_metrics(metrics_json: &str) -> Result<RecordBatchMetrics> {
182    serde_json::from_str(metrics_json).map_err(|e| {
183        IllegalFlightMessagesSnafu {
184            reason: format!("Invalid terminal metrics message: {e}"),
185        }
186        .build()
187    })
188}
189
190struct StreamWithMetrics {
191    stream: common_recordbatch::SendableRecordBatchStream,
192    metrics: OutputMetrics,
193}
194
195impl StreamWithMetrics {
196    fn new(stream: common_recordbatch::SendableRecordBatchStream, metrics: OutputMetrics) -> Self {
197        Self { stream, metrics }
198    }
199
200    fn sync_terminal_metrics(&self) {
201        self.metrics.update(self.stream.metrics());
202    }
203}
204
205impl RecordBatchStream for StreamWithMetrics {
206    fn name(&self) -> &str {
207        self.stream.name()
208    }
209
210    fn schema(&self) -> datatypes::schema::SchemaRef {
211        self.stream.schema()
212    }
213
214    fn output_ordering(&self) -> Option<&[OrderOption]> {
215        self.stream.output_ordering()
216    }
217
218    fn metrics(&self) -> Option<RecordBatchMetrics> {
219        self.sync_terminal_metrics();
220        self.metrics.get()
221    }
222}
223
224impl Stream for StreamWithMetrics {
225    type Item = common_recordbatch::error::Result<RecordBatch>;
226
227    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
228        let polled = Pin::new(&mut self.stream).poll_next(cx);
229        if let Poll::Ready(None) = &polled {
230            self.sync_terminal_metrics();
231            self.metrics.mark_ready();
232        }
233        polled
234    }
235
236    fn size_hint(&self) -> (usize, Option<usize>) {
237        self.stream.size_hint()
238    }
239}
240
241fn attach_terminal_metrics(output: Output, terminal_metrics: &OutputMetrics) -> Output {
242    let Output { data, meta } = output;
243    let data = match data {
244        common_query::OutputData::Stream(stream) => {
245            terminal_metrics.update(stream.metrics());
246            common_query::OutputData::Stream(Box::pin(StreamWithMetrics::new(
247                stream,
248                terminal_metrics.clone(),
249            )))
250        }
251        other => {
252            terminal_metrics.mark_ready();
253            other
254        }
255    };
256    Output::new(data, meta)
257}
258
259async fn output_from_flight_message_stream<S>(
260    mut flight_message_stream: S,
261) -> Result<OutputWithMetrics>
262where
263    S: Stream<Item = Result<FlightMessage>> + Send + Unpin + 'static,
264{
265    let Some(first_flight_message) = flight_message_stream.next().await else {
266        return IllegalFlightMessagesSnafu {
267            reason: "Expect the response not to be empty",
268        }
269        .fail();
270    };
271
272    let first_flight_message = first_flight_message?;
273
274    match first_flight_message {
275        FlightMessage::AffectedRows { rows, metrics } => {
276            let terminal_metrics = OutputMetrics::new();
277            if let Some(metrics) = metrics {
278                terminal_metrics.update(Some(parse_terminal_metrics(&metrics)?));
279            }
280            let next_message = flight_message_stream.next().await.transpose()?;
281            match next_message {
282                None => terminal_metrics.mark_ready(),
283                Some(FlightMessage::Metrics(s)) if terminal_metrics.get().is_none() => {
284                    terminal_metrics.update(Some(parse_terminal_metrics(&s)?));
285                    terminal_metrics.mark_ready();
286                }
287                Some(FlightMessage::Metrics(_)) => {
288                    return IllegalFlightMessagesSnafu {
289                        reason: "'AffectedRows' Flight metadata already carries Metrics and cannot be followed by another Metrics message",
290                    }
291                    .fail();
292                }
293                Some(other) => {
294                    return IllegalFlightMessagesSnafu {
295                        reason: format!(
296                            "'AffectedRows' Flight message can only be followed by a Metrics message, got {other:?}"
297                        ),
298                    }
299                    .fail();
300                }
301            }
302            Ok(OutputWithMetrics {
303                output: Output::new_with_affected_rows(rows),
304                metrics: terminal_metrics,
305            })
306        }
307        FlightMessage::RecordBatch(_) | FlightMessage::Metrics(_) => IllegalFlightMessagesSnafu {
308            reason: "The first flight message cannot be a RecordBatch or Metrics message",
309        }
310        .fail(),
311        FlightMessage::Schema(schema) => {
312            let metrics = Arc::new(ArcSwapOption::from(None));
313            let metrics_ref = metrics.clone();
314            let schema = Arc::new(
315                datatypes::schema::Schema::try_from(schema).context(error::ConvertSchemaSnafu)?,
316            );
317            let schema_cloned = schema.clone();
318            let stream = Box::pin(stream!({
319                while let Some(flight_message_item) = flight_message_stream.next().await {
320                    let flight_message = match flight_message_item {
321                        Ok(message) => message,
322                        Err(e) => {
323                            yield Err(BoxedError::new(e)).context(ExternalSnafu);
324                            break;
325                        }
326                    };
327
328                    match flight_message {
329                        FlightMessage::RecordBatch(arrow_batch) => {
330                            yield Ok(RecordBatch::from_df_record_batch(
331                                schema_cloned.clone(),
332                                arrow_batch,
333                            ))
334                        }
335                        FlightMessage::Metrics(s) => {
336                            match parse_terminal_metrics(&s) {
337                                Ok(m) => {
338                                    metrics_ref.swap(Some(Arc::new(m)));
339                                }
340                                Err(e) => {
341                                    yield Err(BoxedError::new(e)).context(ExternalSnafu);
342                                }
343                            };
344                        }
345                        FlightMessage::AffectedRows { .. } | FlightMessage::Schema(_) => {
346                            yield IllegalFlightMessagesSnafu {
347                                reason: format!(
348                                    "A Schema message must be succeeded exclusively by a set of RecordBatch messages, flight_message: {:?}",
349                                    flight_message
350                                )
351                            }
352                            .fail()
353                            .map_err(BoxedError::new)
354                            .context(ExternalSnafu);
355                            break;
356                        }
357                    }
358                }
359            }));
360            let record_batch_stream = RecordBatchStreamWrapper {
361                schema,
362                stream,
363                output_ordering: None,
364                metrics,
365                span: Span::current(),
366            };
367            Ok(OutputWithMetrics::from_output(Output::new_with_stream(
368                Box::pin(record_batch_stream),
369            )))
370        }
371    }
372}
373
374#[derive(Clone, Debug, Default)]
375pub struct Database {
376    // The "catalog" and "schema" to be used in processing the requests at the server side.
377    // They are the "hint" or "context", just like how the "database" in "USE" statement is treated in MySQL.
378    // They will be carried in the request header.
379    catalog: String,
380    schema: String,
381    // The dbname follows naming rule as out mysql, postgres and http
382    // protocol. The server treat dbname in priority of catalog/schema.
383    dbname: String,
384    // The time zone indicates the time zone where the user is located.
385    // Some queries need to be aware of the user's time zone to perform some specific actions.
386    timezone: String,
387
388    client: Client,
389    ctx: FlightContext,
390}
391
392pub struct DatabaseClient {
393    pub addr: String,
394    pub inner: GreptimeDatabaseClient<Channel>,
395}
396
397impl DatabaseClient {
398    /// Returns a closure that logs the error when the request fails.
399    pub fn inspect_err<'a>(&'a self, context: &'a str) -> impl Fn(&tonic::Status) + 'a {
400        let addr = &self.addr;
401        move |status| {
402            error!("Failed to {context} request, peer: {addr}, status: {status:?}");
403        }
404    }
405}
406
407fn make_database_client(client: &Client) -> Result<DatabaseClient> {
408    let (addr, channel) = client.find_channel()?;
409    Ok(DatabaseClient {
410        addr,
411        inner: GreptimeDatabaseClient::new(channel)
412            .max_decoding_message_size(client.max_grpc_recv_message_size())
413            .max_encoding_message_size(client.max_grpc_send_message_size()),
414    })
415}
416
417impl Database {
418    /// Create database service client using catalog and schema
419    pub fn new(catalog: impl Into<String>, schema: impl Into<String>, client: Client) -> Self {
420        Self {
421            catalog: catalog.into(),
422            schema: schema.into(),
423            dbname: String::default(),
424            timezone: String::default(),
425            client,
426            ctx: FlightContext::default(),
427        }
428    }
429
430    /// Create database service client using dbname.
431    ///
432    /// This API is designed for external usage. `dbname` is:
433    ///
434    /// - the name of database when using GreptimeDB standalone or cluster
435    /// - the name provided by GreptimeCloud or other multi-tenant GreptimeDB
436    ///   environment
437    pub fn new_with_dbname(dbname: impl Into<String>, client: Client) -> Self {
438        Self {
439            catalog: String::default(),
440            schema: String::default(),
441            timezone: String::default(),
442            dbname: dbname.into(),
443            client,
444            ctx: FlightContext::default(),
445        }
446    }
447
448    /// Set the catalog for the database client.
449    pub fn set_catalog(&mut self, catalog: impl Into<String>) {
450        self.catalog = catalog.into();
451    }
452
453    fn catalog_or_default(&self) -> &str {
454        if self.catalog.is_empty() {
455            DEFAULT_CATALOG_NAME
456        } else {
457            &self.catalog
458        }
459    }
460
461    /// Set the schema for the database client.
462    pub fn set_schema(&mut self, schema: impl Into<String>) {
463        self.schema = schema.into();
464    }
465
466    fn schema_or_default(&self) -> &str {
467        if self.schema.is_empty() {
468            DEFAULT_SCHEMA_NAME
469        } else {
470            &self.schema
471        }
472    }
473
474    /// Set the timezone for the database client.
475    pub fn set_timezone(&mut self, timezone: impl Into<String>) {
476        self.timezone = timezone.into();
477    }
478
479    /// Set the auth scheme for the database client.
480    pub fn set_auth(&mut self, auth: AuthScheme) {
481        self.ctx.auth_header = Some(AuthHeader {
482            auth_scheme: Some(auth),
483        });
484    }
485
486    /// Make an InsertRequests request to the database.
487    pub async fn insert(&self, requests: InsertRequests) -> Result<u32> {
488        self.handle(Request::Inserts(requests)).await
489    }
490
491    /// Make an InsertRequests request to the database with hints.
492    pub async fn insert_with_hints(
493        &self,
494        requests: InsertRequests,
495        hints: &[(&str, &str)],
496    ) -> Result<u32> {
497        let mut client = make_database_client(&self.client)?;
498        let request = self.to_rpc_request(Request::Inserts(requests));
499
500        let mut request = tonic::Request::new(request);
501        let metadata = request.metadata_mut();
502        Self::put_hints(metadata, hints)?;
503
504        let response = client
505            .inner
506            .handle(request)
507            .await
508            .inspect_err(client.inspect_err("insert_with_hints"))?
509            .into_inner();
510        from_grpc_response(response)
511    }
512
513    /// Make a RowInsertRequests request to the database.
514    pub async fn row_inserts(&self, requests: RowInsertRequests) -> Result<u32> {
515        self.handle(Request::RowInserts(requests)).await
516    }
517
518    /// Make a RowInsertRequests request to the database with hints.
519    pub async fn row_inserts_with_hints(
520        &self,
521        requests: RowInsertRequests,
522        hints: &[(&str, &str)],
523    ) -> Result<u32> {
524        let mut client = make_database_client(&self.client)?;
525        let request = self.to_rpc_request(Request::RowInserts(requests));
526
527        let mut request = tonic::Request::new(request);
528        let metadata = request.metadata_mut();
529        Self::put_hints(metadata, hints)?;
530
531        let response = client
532            .inner
533            .handle(request)
534            .await
535            .inspect_err(client.inspect_err("row_inserts_with_hints"))?
536            .into_inner();
537        from_grpc_response(response)
538    }
539
540    fn put_hints(metadata: &mut MetadataMap, hints: &[(&str, &str)]) -> Result<()> {
541        let Some(value) = hints
542            .iter()
543            .map(|(k, v)| format!("{}={}", k, v))
544            .reduce(|a, b| format!("{},{}", a, b))
545        else {
546            return Ok(());
547        };
548
549        let key = AsciiMetadataKey::from_static("x-greptime-hints");
550        let value = AsciiMetadataValue::from_str(&value).context(InvalidTonicMetadataValueSnafu)?;
551        metadata.insert(key, value);
552        Ok(())
553    }
554
555    fn put_flow_extensions(
556        metadata: &mut MetadataMap,
557        flow_extensions: &[(&str, &str)],
558    ) -> Result<()> {
559        if flow_extensions.is_empty() {
560            return Ok(());
561        }
562
563        let value = serde_json::to_string(&flow_extensions.to_vec())
564            .expect("flow extension pairs should serialize");
565        Self::put_metadata_value(metadata, FLOW_EXTENSIONS_METADATA_KEY, value)
566    }
567
568    fn put_snapshot_seqs(
569        metadata: &mut MetadataMap,
570        snapshot_seqs: &std::collections::HashMap<u64, u64>,
571    ) -> Result<()> {
572        if snapshot_seqs.is_empty() {
573            return Ok(());
574        }
575
576        let value =
577            serde_json::to_string(snapshot_seqs).expect("snapshot sequence map should serialize");
578        Self::put_metadata_value(metadata, SNAPSHOT_SEQS_METADATA_KEY, value)
579    }
580
581    fn put_metadata_value(
582        metadata: &mut MetadataMap,
583        key: &'static str,
584        value: String,
585    ) -> Result<()> {
586        let key = AsciiMetadataKey::from_static(key);
587        let value = AsciiMetadataValue::from_str(&value).context(InvalidTonicMetadataValueSnafu)?;
588        metadata.insert(key, value);
589        Ok(())
590    }
591
592    /// Make a request to the database.
593    pub async fn handle(&self, request: Request) -> Result<u32> {
594        let mut client = make_database_client(&self.client)?;
595        let request = self.to_rpc_request(request);
596        let response = client
597            .inner
598            .handle(request)
599            .await
600            .inspect_err(client.inspect_err("handle"))?
601            .into_inner();
602        from_grpc_response(response)
603    }
604
605    /// Retry if connection fails, max_retries is the max number of retries, so the total wait time
606    /// is `max_retries * GRPC_CONN_TIMEOUT`
607    pub async fn handle_with_retry(
608        &self,
609        request: Request,
610        max_retries: u32,
611        hints: &[(&str, &str)],
612    ) -> Result<u32> {
613        let mut client = make_database_client(&self.client)?;
614        let mut retries = 0;
615
616        let request = self.to_rpc_request(request);
617
618        loop {
619            let mut tonic_request = tonic::Request::new(request.clone());
620            let metadata = tonic_request.metadata_mut();
621            Self::put_hints(metadata, hints)?;
622            let raw_response = client
623                .inner
624                .handle(tonic_request)
625                .await
626                .inspect_err(client.inspect_err("handle"));
627            match (raw_response, retries < max_retries) {
628                (Ok(resp), _) => return from_grpc_response(resp.into_inner()),
629                (Err(err), true) => {
630                    // determine if the error is retryable
631                    if is_grpc_retryable(&err) {
632                        // retry
633                        retries += 1;
634                        warn!("Retrying {} times with error = {:?}", retries, err);
635                        continue;
636                    } else {
637                        error!(
638                            err; "Failed to send request to grpc handle, retries = {}, not retryable error, aborting",
639                            retries
640                        );
641                        return Err(err.into());
642                    }
643                }
644                (Err(err), false) => {
645                    error!(
646                        err; "Failed to send request to grpc handle after {} retries",
647                        retries,
648                    );
649                    return Err(err.into());
650                }
651            }
652        }
653    }
654
655    #[inline]
656    fn to_rpc_request(&self, request: Request) -> GreptimeRequest {
657        GreptimeRequest {
658            header: Some(RequestHeader {
659                catalog: self.catalog.clone(),
660                schema: self.schema.clone(),
661                authorization: self.ctx.auth_header.clone(),
662                dbname: self.dbname.clone(),
663                timezone: self.timezone.clone(),
664                // TODO(Taylor-lagrange): add client grpc tracing
665                tracing_context: W3cTrace::new(),
666            }),
667            request: Some(request),
668        }
669    }
670
671    /// Executes a SQL query without any hints.
672    pub async fn sql<S>(&self, sql: S) -> Result<Output>
673    where
674        S: AsRef<str>,
675    {
676        self.sql_with_hint(sql, &[]).await
677    }
678
679    /// Executes a SQL query with optional hints for query optimization.
680    pub async fn sql_with_hint<S>(&self, sql: S, hints: &[(&str, &str)]) -> Result<Output>
681    where
682        S: AsRef<str>,
683    {
684        let request = Request::Query(QueryRequest {
685            query: Some(Query::Sql(sql.as_ref().to_string())),
686        });
687        self.do_get(request, hints, &[], &Default::default())
688            .await
689            .map(OutputWithMetrics::into_output)
690    }
691
692    /// Executes a SQL query and returns the output with terminal metrics.
693    ///
694    /// For stream outputs, callers must consume the stream before reading final
695    /// terminal metrics from [`OutputWithMetrics::metrics`].
696    pub async fn sql_with_terminal_metrics<S>(
697        &self,
698        sql: S,
699        hints: &[(&str, &str)],
700    ) -> Result<OutputWithMetrics>
701    where
702        S: AsRef<str>,
703    {
704        self.query_with_terminal_metrics_and_flow_extensions(
705            QueryRequest {
706                query: Some(Query::Sql(sql.as_ref().to_string())),
707            },
708            hints,
709            &[],
710            &Default::default(),
711        )
712        .await
713    }
714
715    /// Executes a logical plan directly without SQL parsing.
716    pub async fn logical_plan(&self, logical_plan: Vec<u8>) -> Result<Output> {
717        self.query_with_terminal_metrics_and_flow_extensions(
718            QueryRequest {
719                query: Some(Query::LogicalPlan(logical_plan)),
720            },
721            &[],
722            &[],
723            &Default::default(),
724        )
725        .await
726        .map(OutputWithMetrics::into_output)
727    }
728
729    /// Executes a query and carries flow extensions through Flight metadata.
730    ///
731    /// This is the lower-level terminal-metrics API for Flow callers that need
732    /// to pass JSON-bearing flow extensions without going through hint metadata.
733    pub async fn query_with_terminal_metrics_and_flow_extensions(
734        &self,
735        request: QueryRequest,
736        hints: &[(&str, &str)],
737        flow_extensions: &[(&str, &str)],
738        snapshot_seqs: &std::collections::HashMap<u64, u64>,
739    ) -> Result<OutputWithMetrics> {
740        self.do_get(
741            Request::Query(request),
742            hints,
743            flow_extensions,
744            snapshot_seqs,
745        )
746        .await
747    }
748
749    /// Creates a new table using the provided table expression.
750    pub async fn create(&self, expr: CreateTableExpr) -> Result<Output> {
751        let request = Request::Ddl(DdlRequest {
752            expr: Some(DdlExpr::CreateTable(expr)),
753        });
754        self.do_get(request, &[], &[], &Default::default())
755            .await
756            .map(OutputWithMetrics::into_output)
757    }
758
759    /// Alters an existing table using the provided alter expression.
760    pub async fn alter(&self, expr: AlterTableExpr) -> Result<Output> {
761        let request = Request::Ddl(DdlRequest {
762            expr: Some(DdlExpr::AlterTable(expr)),
763        });
764        self.do_get(request, &[], &[], &Default::default())
765            .await
766            .map(OutputWithMetrics::into_output)
767    }
768
769    async fn do_get(
770        &self,
771        request: Request,
772        hints: &[(&str, &str)],
773        flow_extensions: &[(&str, &str)],
774        snapshot_seqs: &std::collections::HashMap<u64, u64>,
775    ) -> Result<OutputWithMetrics> {
776        let request = self.to_rpc_request(request);
777        let request = Ticket {
778            ticket: request.encode_to_vec().into(),
779        };
780
781        let mut request = tonic::Request::new(request);
782        let metadata = request.metadata_mut();
783        Self::put_hints(metadata, hints)?;
784        Self::put_flow_extensions(metadata, flow_extensions)?;
785        Self::put_snapshot_seqs(metadata, snapshot_seqs)?;
786
787        let mut client = self.client.make_flight_client(false, false)?;
788
789        let response = client.mut_inner().do_get(request).await.or_else(|e| {
790            let tonic_code = e.code();
791            let e: Error = e.into();
792            error!(
793                "Failed to do Flight get, addr: {}, code: {}, source: {:?}",
794                client.addr(),
795                tonic_code,
796                e
797            );
798            Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
799                addr: client.addr().to_string(),
800                tonic_code,
801            })
802        })?;
803
804        let flight_data_stream = response.into_inner();
805        let mut decoder = FlightDecoder::default();
806
807        let flight_message_stream = flight_data_stream.filter_map(move |flight_data| {
808            future::ready(decode_flight_data(&mut decoder, flight_data))
809        });
810
811        output_from_flight_message_stream(flight_message_stream).await
812    }
813
814    /// Ingest a stream of [RecordBatch]es that belong to a table, using Arrow Flight's "`DoPut`"
815    /// method. The return value is also a stream, produces [DoPutResponse]s.
816    pub async fn do_put(&self, stream: FlightDataStream) -> Result<DoPutResponseStream> {
817        let mut request = tonic::Request::new(stream);
818
819        if let Some(AuthHeader {
820            auth_scheme: Some(AuthScheme::Basic(Basic { username, password })),
821        }) = &self.ctx.auth_header
822        {
823            let encoded = BASE64_STANDARD.encode(format!("{username}:{password}"));
824            let value = MetadataValue::from_str(&format!("Basic {encoded}"))
825                .context(InvalidTonicMetadataValueSnafu)?;
826            request.metadata_mut().insert("x-greptime-auth", value);
827        }
828
829        let db_to_put = if !self.dbname.is_empty() {
830            &self.dbname
831        } else {
832            &build_db_string(self.catalog_or_default(), self.schema_or_default())
833        };
834        request.metadata_mut().insert(
835            "x-greptime-db-name",
836            MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?,
837        );
838
839        let mut client = self.client.make_flight_client(false, false)?;
840        let response = client.mut_inner().do_put(request).await?;
841        let response = response
842            .into_inner()
843            .map_err(Into::into)
844            .and_then(|x| future::ready(DoPutResponse::try_from(x).context(ConvertFlightDataSnafu)))
845            .boxed();
846        Ok(response)
847    }
848}
849
850/// by grpc standard, only `Unavailable` is retryable, see: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc
851pub fn is_grpc_retryable(err: &tonic::Status) -> bool {
852    matches!(err.code(), tonic::Code::Unavailable)
853}
854
855#[derive(Default, Debug, Clone)]
856struct FlightContext {
857    auth_header: Option<AuthHeader>,
858}
859
860#[cfg(test)]
861mod tests {
862    use std::sync::Arc;
863    use std::task::{Context, Poll};
864
865    use api::v1::auth_header::AuthScheme;
866    use api::v1::{AuthHeader, Basic};
867    use common_error::ext::{ErrorExt, RetryHint};
868    use common_error::status_code::StatusCode;
869    use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT};
870    use common_query::OutputData;
871    use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream};
872    use datatypes::prelude::{ConcreteDataType, VectorRef};
873    use datatypes::schema::{ColumnSchema, Schema};
874    use datatypes::vectors::Int32Vector;
875    use futures_util::StreamExt;
876    use tonic::codegen::http::{HeaderMap, HeaderValue};
877    use tonic::metadata::MetadataMap;
878    use tonic::{Code, Status};
879
880    use super::*;
881    use crate::error::TonicSnafu;
882
883    struct MockMetricsStream {
884        schema: datatypes::schema::SchemaRef,
885        batch: Option<RecordBatch>,
886        metrics: RecordBatchMetrics,
887        terminal_metrics_only: bool,
888    }
889
890    impl Stream for MockMetricsStream {
891        type Item = common_recordbatch::error::Result<RecordBatch>;
892
893        fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
894            Poll::Ready(self.batch.take().map(Ok))
895        }
896    }
897
898    impl RecordBatchStream for MockMetricsStream {
899        fn name(&self) -> &str {
900            "MockMetricsStream"
901        }
902
903        fn schema(&self) -> datatypes::schema::SchemaRef {
904            self.schema.clone()
905        }
906
907        fn output_ordering(&self) -> Option<&[OrderOption]> {
908            None
909        }
910
911        fn metrics(&self) -> Option<RecordBatchMetrics> {
912            if self.terminal_metrics_only && self.batch.is_some() {
913                return None;
914            }
915            Some(self.metrics.clone())
916        }
917    }
918
919    fn terminal_metrics_json() -> String {
920        terminal_metrics_json_with_seq(42)
921    }
922
923    fn terminal_metrics_json_with_seq(seq: u64) -> String {
924        serde_json::to_string(&RecordBatchMetrics {
925            region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
926                region_id: 7,
927                watermark: Some(seq),
928            }],
929            ..Default::default()
930        })
931        .unwrap()
932    }
933
934    #[test]
935    fn test_put_flow_extensions_preserves_comma_bearing_values() {
936        let mut metadata = MetadataMap::new();
937        Database::put_flow_extensions(
938            &mut metadata,
939            &[
940                ("flow.return_region_seq", "true"),
941                ("flow.incremental_after_seqs", r#"{"1":10,"2":20}"#),
942            ],
943        )
944        .unwrap();
945
946        let value = metadata
947            .get(FLOW_EXTENSIONS_METADATA_KEY)
948            .unwrap()
949            .to_str()
950            .unwrap();
951        let decoded: Vec<(String, String)> = serde_json::from_str(value).unwrap();
952        assert_eq!(
953            decoded,
954            vec![
955                ("flow.return_region_seq".to_string(), "true".to_string()),
956                (
957                    "flow.incremental_after_seqs".to_string(),
958                    r#"{"1":10,"2":20}"#.to_string()
959                ),
960            ]
961        );
962    }
963
964    #[test]
965    fn test_put_snapshot_seqs_preserves_u64_precision() {
966        let mut metadata = MetadataMap::new();
967        let snapshot_seqs = std::collections::HashMap::from([
968            (u64::MAX, u64::MAX - 1),
969            (9_007_199_254_740_993_u64, 9_007_199_254_740_995_u64),
970        ]);
971
972        Database::put_snapshot_seqs(&mut metadata, &snapshot_seqs).unwrap();
973
974        let value = metadata
975            .get(SNAPSHOT_SEQS_METADATA_KEY)
976            .unwrap()
977            .to_str()
978            .unwrap();
979        let decoded: std::collections::HashMap<u64, u64> = serde_json::from_str(value).unwrap();
980        assert_eq!(decoded, snapshot_seqs);
981    }
982
983    #[test]
984    fn test_flight_ctx() {
985        let mut ctx = FlightContext::default();
986        assert!(ctx.auth_header.is_none());
987
988        let basic = AuthScheme::Basic(Basic {
989            username: "u".to_string(),
990            password: "p".to_string(),
991        });
992
993        ctx.auth_header = Some(AuthHeader {
994            auth_scheme: Some(basic),
995        });
996
997        assert!(matches!(
998            ctx.auth_header,
999            Some(AuthHeader {
1000                auth_scheme: Some(AuthScheme::Basic(_)),
1001            })
1002        ));
1003    }
1004
1005    #[test]
1006    fn test_from_tonic_status() {
1007        let expected = TonicSnafu {
1008            code: StatusCode::Internal,
1009            msg: "blabla".to_string(),
1010            tonic_code: Code::Internal,
1011            retry_hint: RetryHint::NonRetryable,
1012        }
1013        .build();
1014
1015        let status = Status::new(Code::Internal, "blabla");
1016        let actual: Error = status.into();
1017
1018        assert_eq!(expected.to_string(), actual.to_string());
1019        assert_eq!(expected.retry_hint(), actual.retry_hint());
1020        assert_eq!(expected.should_retry(), actual.should_retry());
1021    }
1022
1023    #[test]
1024    fn test_from_tonic_status_with_retry_hint() {
1025        let mut headers = HeaderMap::new();
1026        headers.insert(
1027            GREPTIME_DB_HEADER_ERROR_CODE,
1028            HeaderValue::from(StatusCode::Internal as u32),
1029        );
1030        headers.insert(
1031            GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
1032            HeaderValue::from_static(RetryHint::Retryable.as_str()),
1033        );
1034        let status =
1035            Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1036
1037        let actual: Error = status.into();
1038
1039        assert_eq!(actual.retry_hint(), RetryHint::Retryable);
1040        assert!(actual.should_retry());
1041    }
1042
1043    #[test]
1044    fn test_from_tonic_status_fallback() {
1045        let mut headers = HeaderMap::new();
1046        headers.insert(
1047            GREPTIME_DB_HEADER_ERROR_CODE,
1048            HeaderValue::from(StatusCode::InvalidArguments as u32),
1049        );
1050        let status =
1051            Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1052
1053        let actual: Error = status.into();
1054
1055        assert_eq!(actual.retry_hint(), RetryHint::NonRetryable);
1056        assert!(!actual.should_retry());
1057    }
1058
1059    #[test]
1060    fn test_should_retry_preserves_transport_retry() {
1061        let status = Status::new(Code::Unavailable, "blabla");
1062        let actual: Error = status.into();
1063
1064        assert!(actual.should_retry());
1065    }
1066
1067    #[tokio::test]
1068    async fn test_query_with_terminal_metrics_tracks_terminal_only_metrics() {
1069        let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1070            "v",
1071            ConcreteDataType::int32_datatype(),
1072            false,
1073        )]));
1074        let batch = RecordBatch::new(
1075            schema.clone(),
1076            vec![Arc::new(Int32Vector::from_slice([1, 2])) as VectorRef],
1077        )
1078        .unwrap();
1079        let output = Output::new_with_stream(Box::pin(MockMetricsStream {
1080            schema,
1081            batch: Some(batch),
1082            metrics: RecordBatchMetrics {
1083                region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
1084                    region_id: 7,
1085                    watermark: Some(42),
1086                }],
1087                ..Default::default()
1088            },
1089            terminal_metrics_only: true,
1090        }));
1091
1092        let result = OutputWithMetrics::from_output(output);
1093        let terminal_metrics = result.metrics.clone();
1094        assert!(!terminal_metrics.is_ready());
1095        assert!(terminal_metrics.get().is_none());
1096
1097        let OutputData::Stream(mut stream) = result.output.data else {
1098            panic!("expected stream output");
1099        };
1100        while stream.next().await.is_some() {}
1101
1102        assert!(terminal_metrics.is_ready());
1103        assert_eq!(
1104            terminal_metrics.participating_regions(),
1105            Some(std::collections::BTreeSet::from([7_u64]))
1106        );
1107        assert_eq!(
1108            terminal_metrics.region_watermark_map(),
1109            Some(std::collections::HashMap::from([(7_u64, 42_u64)]))
1110        );
1111    }
1112
1113    #[test]
1114    fn test_parse_terminal_metrics_rejects_invalid_json() {
1115        assert!(parse_terminal_metrics("{not-json}").is_err());
1116    }
1117
1118    #[tokio::test]
1119    async fn test_affected_rows_inline_metrics_are_parsed() {
1120        let output = output_from_flight_message_stream(futures_util::stream::iter(vec![Ok(
1121            FlightMessage::AffectedRows {
1122                rows: 3,
1123                metrics: Some(terminal_metrics_json()),
1124            },
1125        )]
1126            as Vec<Result<FlightMessage>>))
1127        .await
1128        .unwrap();
1129
1130        assert!(matches!(output.output.data, OutputData::AffectedRows(3)));
1131        assert!(output.metrics.is_ready());
1132        assert_eq!(
1133            output.metrics.region_watermark_map(),
1134            Some(std::collections::HashMap::from([(7, 42)]))
1135        );
1136    }
1137
1138    #[tokio::test]
1139    async fn test_affected_rows_inline_metrics_rejects_trailing_metrics() {
1140        let metrics_json = terminal_metrics_json();
1141        let err = output_from_flight_message_stream(futures_util::stream::iter(vec![
1142            Ok(FlightMessage::AffectedRows {
1143                rows: 3,
1144                metrics: Some(metrics_json.clone()),
1145            }),
1146            Ok(FlightMessage::Metrics(metrics_json)),
1147        ]
1148            as Vec<Result<FlightMessage>>))
1149        .await
1150        .unwrap_err();
1151
1152        assert!(
1153            err.to_string().contains("already carries Metrics"),
1154            "unexpected error: {err:?}"
1155        );
1156    }
1157
1158    #[tokio::test]
1159    async fn test_invalid_terminal_metrics_after_record_batch_yields_batch_then_error() {
1160        let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1161            "v",
1162            ConcreteDataType::int32_datatype(),
1163            false,
1164        )]));
1165        let batch = RecordBatch::new(
1166            schema.clone(),
1167            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1168        )
1169        .unwrap();
1170        let output = output_from_flight_message_stream(futures_util::stream::iter(vec![
1171            Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1172            Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1173            Ok(FlightMessage::Metrics("{not-json}".to_string())),
1174        ]
1175            as Vec<Result<FlightMessage>>))
1176        .await
1177        .unwrap();
1178        let terminal_metrics = output.metrics.clone();
1179        let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1180            panic!("expected stream output");
1181        };
1182
1183        let batch = record_batch_stream.next().await.unwrap().unwrap();
1184        assert_eq!(batch.num_rows(), 1);
1185
1186        let err = record_batch_stream.next().await.unwrap().unwrap_err();
1187        assert_eq!("External error", err.to_string());
1188        assert!(
1189            format!("{err:?}").contains("Invalid terminal metrics message"),
1190            "unexpected error: {err:?}"
1191        );
1192        assert!(record_batch_stream.next().await.is_none());
1193        assert!(terminal_metrics.is_ready());
1194        assert!(terminal_metrics.get().is_none());
1195    }
1196
1197    #[tokio::test]
1198    async fn test_record_batch_stream_continues_after_partial_metrics() {
1199        let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1200            "v",
1201            ConcreteDataType::int32_datatype(),
1202            false,
1203        )]));
1204        let first_batch = RecordBatch::new(
1205            schema.clone(),
1206            vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1207        )
1208        .unwrap();
1209        let second_batch = RecordBatch::new(
1210            schema.clone(),
1211            vec![Arc::new(Int32Vector::from_slice([2])) as VectorRef],
1212        )
1213        .unwrap();
1214        let output = output_from_flight_message_stream(futures_util::stream::iter(vec![
1215            Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1216            Ok(FlightMessage::RecordBatch(
1217                first_batch.into_df_record_batch(),
1218            )),
1219            Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(1))),
1220            Ok(FlightMessage::RecordBatch(
1221                second_batch.into_df_record_batch(),
1222            )),
1223            Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(2))),
1224        ]
1225            as Vec<Result<FlightMessage>>))
1226        .await
1227        .unwrap();
1228        let terminal_metrics = output.metrics.clone();
1229        let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1230            panic!("expected stream output");
1231        };
1232
1233        let first_batch = record_batch_stream.next().await.unwrap().unwrap();
1234        assert_eq!(first_batch.num_rows(), 1);
1235        let second_batch = record_batch_stream.next().await.unwrap().unwrap();
1236        assert_eq!(second_batch.num_rows(), 1);
1237        assert!(record_batch_stream.next().await.is_none());
1238
1239        assert!(terminal_metrics.is_ready());
1240        assert_eq!(
1241            terminal_metrics.region_watermark_map(),
1242            Some(std::collections::HashMap::from([(7, 2)]))
1243        );
1244    }
1245
1246    #[test]
1247    fn test_output_metrics_distinguishes_empty_region_watermarks_from_absence() {
1248        let metrics = OutputMetrics::default();
1249        metrics.update(Some(RecordBatchMetrics::default()));
1250
1251        assert_eq!(
1252            metrics.participating_regions(),
1253            Some(std::collections::BTreeSet::new())
1254        );
1255        assert_eq!(
1256            metrics.region_watermark_map(),
1257            Some(std::collections::HashMap::new())
1258        );
1259    }
1260}