1use std::collections::HashMap;
18use std::future::Future;
19use std::str::FromStr;
20use std::sync::{Arc, RwLock};
21use std::time::Instant;
22
23use api::helper::request_type;
24use api::v1::greptime_request::Request as QueryRequest;
25use api::v1::{GreptimeRequest, RequestHeader};
26use auth::UserProviderRef;
27use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
28use common_catalog::parse_catalog_and_schema_from_db_string;
29use common_error::ext::ErrorExt;
30use common_error::status_code::StatusCode;
31use common_grpc::flight::do_put::DoPutResponse;
32use common_query::Output;
33use common_runtime::Runtime;
34use common_runtime::runtime::RuntimeTrait;
35use common_session::ReadPreference;
36use common_telemetry::tracing_context::{FutureExt, TracingContext};
37use common_telemetry::{debug, error, tracing, warn};
38use common_time::timezone::parse_timezone;
39use futures_util::StreamExt;
40use session::context::{Channel, QueryContextBuilder, QueryContextRef};
41use session::hints::{READ_PREFERENCE_HINT, is_reserved_extension_key};
42use snafu::{OptionExt, ResultExt};
43use tokio::sync::mpsc;
44use tokio::sync::mpsc::error::TrySendError;
45use tonic::Status;
46
47use crate::error::{InvalidQuerySnafu, JoinTaskSnafu, Result, UnknownHintSnafu};
48use crate::grpc::flight::PutRecordBatchRequestStream;
49use crate::grpc::{FlightCompression, TonicResult, context_auth};
50use crate::metrics::{self, METRIC_SERVER_GRPC_DB_REQUEST_TIMER};
51use crate::query_handler::grpc::ServerGrpcQueryHandlerRef;
52
53#[derive(Clone)]
54pub struct GreptimeRequestHandler {
55 handler: ServerGrpcQueryHandlerRef,
56 pub(crate) user_provider: Option<UserProviderRef>,
57 runtime: Option<Runtime>,
58 pub(crate) flight_compression: FlightCompression,
59}
60
61impl GreptimeRequestHandler {
62 pub fn new(
63 handler: ServerGrpcQueryHandlerRef,
64 user_provider: Option<UserProviderRef>,
65 runtime: Option<Runtime>,
66 flight_compression: FlightCompression,
67 ) -> Self {
68 Self {
69 handler,
70 user_provider,
71 runtime,
72 flight_compression,
73 }
74 }
75
76 #[tracing::instrument(skip_all, fields(protocol = "grpc", request_type = get_request_type(&request)))]
77 pub(crate) async fn handle_request(
78 &self,
79 request: GreptimeRequest,
80 hints: Vec<(String, String)>,
81 ) -> Result<Output> {
82 let header = request.header.as_ref();
83 let query_ctx = create_query_context(Channel::Grpc, header, hints, HashMap::new())?;
84 let query = request.request.context(InvalidQuerySnafu {
85 reason: "Expecting non-empty GreptimeRequest.",
86 })?;
87 self.authenticate_request_with_query_ctx(request.header.as_ref(), &query_ctx)
88 .await?;
89 self.handle_request_with_query_ctx(query, query_ctx).await
90 }
91
92 pub(crate) async fn authenticate_request_with_query_ctx(
93 &self,
94 header: Option<&RequestHeader>,
95 query_ctx: &QueryContextRef,
96 ) -> Result<()> {
97 let user_info = context_auth::auth(self.user_provider.clone(), header, query_ctx).await?;
98 query_ctx.set_current_user(user_info);
99 Ok(())
100 }
101
102 pub(crate) fn handle_request_with_query_ctx(
103 &self,
104 query: QueryRequest,
105 query_ctx: QueryContextRef,
106 ) -> impl Future<Output = Result<Output>> + Send + 'static {
107 let handler = self.handler.clone();
108 let runtime = self.runtime.clone();
109 let request_type = request_type(&query).to_string();
110 let db = query_ctx.get_db_string();
111 let timer = RequestTimer::new(db.clone(), request_type);
112 let tracing_context = TracingContext::from_current_span();
113
114 async move {
115 let result_future = async move {
116 handler
117 .do_query(query, query_ctx)
118 .trace(tracing_context.attach(tracing::info_span!(
119 "GreptimeRequestHandler::handle_request_runtime"
120 )))
121 .await
122 .map_err(|e| {
123 if e.status_code().should_log_error() {
124 let root_error = e.root_cause().unwrap_or(&e);
125 error!(e; "Failed to handle request, error: {}", root_error.to_string());
126 } else {
127 debug!("Failed to handle request, err: {:?}", e);
129 }
130 e
131 })
132 };
133
134 match runtime {
135 Some(runtime) => {
136 runtime
144 .spawn(result_future)
145 .await
146 .context(JoinTaskSnafu)
147 .inspect_err(|e| {
148 timer.record(e.status_code());
149 })?
150 }
151 None => result_future.await,
152 }
153 }
154 }
155
156 pub(crate) async fn put_record_batches(
157 &self,
158 stream: PutRecordBatchRequestStream,
159 result_sender: mpsc::Sender<TonicResult<DoPutResponse>>,
160 query_ctx: QueryContextRef,
161 ) {
162 let handler = self.handler.clone();
163 let runtime = self
164 .runtime
165 .clone()
166 .unwrap_or_else(common_runtime::global_runtime);
167 runtime.spawn(async move {
168 let mut result_stream = handler.handle_put_record_batch_stream(stream, query_ctx);
169
170 while let Some(result) = result_stream.next().await {
171 match &result {
172 Ok(response) => {
173 metrics::GRPC_BULK_INSERT_ELAPSED.observe(response.elapsed_secs());
175 }
176 Err(e) => {
177 error!(e; "Failed to handle flight record batches");
178 }
179 }
180
181 if let Err(e) = result_sender.try_send(result.map_err(Status::from))
182 && let TrySendError::Closed(_) = e
183 {
184 warn!(r#""DoPut" client maybe unreachable, abort handling its message"#);
185 break;
186 }
187 }
188 });
189 }
190}
191
192pub fn get_request_type(request: &GreptimeRequest) -> &'static str {
193 request
194 .request
195 .as_ref()
196 .map(request_type)
197 .unwrap_or_default()
198}
199
200pub(crate) fn create_query_context(
203 channel: Channel,
204 header: Option<&RequestHeader>,
205 mut extensions: Vec<(String, String)>,
206 snapshot_seqs: HashMap<u64, u64>,
207) -> Result<QueryContextRef> {
208 let (catalog, schema) = header
209 .map(|header| {
210 if !header.dbname.is_empty() {
213 parse_catalog_and_schema_from_db_string(&header.dbname)
214 } else {
215 (
216 if !header.catalog.is_empty() {
217 header.catalog.to_lowercase()
218 } else {
219 DEFAULT_CATALOG_NAME.to_string()
220 },
221 if !header.schema.is_empty() {
222 header.schema.to_lowercase()
223 } else {
224 DEFAULT_SCHEMA_NAME.to_string()
225 },
226 )
227 }
228 })
229 .unwrap_or_else(|| {
230 (
231 DEFAULT_CATALOG_NAME.to_string(),
232 DEFAULT_SCHEMA_NAME.to_string(),
233 )
234 });
235 let timezone = parse_timezone(header.map(|h| h.timezone.as_str()));
236 let mut ctx_builder = QueryContextBuilder::default()
237 .current_catalog(catalog)
238 .current_schema(schema)
239 .timezone(timezone)
240 .channel(channel)
241 .snapshot_seqs(Arc::new(RwLock::new(snapshot_seqs)));
242
243 if let Some(x) = extensions
244 .iter()
245 .position(|(k, _)| k == READ_PREFERENCE_HINT)
246 {
247 let (k, v) = extensions.swap_remove(x);
248 let Ok(read_preference) = ReadPreference::from_str(&v) else {
249 return UnknownHintSnafu {
250 hint: format!("{k}={v}"),
251 }
252 .fail();
253 };
254 ctx_builder = ctx_builder.read_preference(read_preference);
255 }
256
257 for (key, value) in extensions {
258 if is_reserved_extension_key(&key) {
259 debug!(
260 key = key.as_str(),
261 "Ignoring reserved external query context extension key"
262 );
263 continue;
264 }
265 ctx_builder = ctx_builder.set_extension(key, value);
266 }
267 Ok(ctx_builder.build().into())
268}
269
270pub(crate) struct RequestTimer {
274 start: Instant,
275 db: String,
276 request_type: String,
277 status_code: StatusCode,
278}
279
280impl RequestTimer {
281 pub fn new(db: String, request_type: String) -> RequestTimer {
283 RequestTimer {
284 start: Instant::now(),
285 db,
286 request_type,
287 status_code: StatusCode::Success,
288 }
289 }
290
291 pub fn record(mut self, status_code: StatusCode) {
293 self.status_code = status_code;
294 }
295}
296
297impl Drop for RequestTimer {
298 fn drop(&mut self) {
299 METRIC_SERVER_GRPC_DB_REQUEST_TIMER
300 .with_label_values(&[
301 self.db.as_str(),
302 self.request_type.as_str(),
303 self.status_code.as_ref(),
304 ])
305 .observe(self.start.elapsed().as_secs_f64());
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use chrono::FixedOffset;
312 use common_error::ext::BoxedError;
313 use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT};
314 use common_time::Timezone;
315 use query::options::FLOW_SCHEDULED_TIME_MILLIS;
316 use session::hints::{
317 INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY, REMOTE_QUERY_ID_EXTENSION_KEY,
318 };
319 use snafu::ResultExt;
320 use tonic::Code;
321
322 use super::*;
323 use crate::error::{ExecuteGrpcRequestSnafu, InvalidParameterSnafu};
324
325 #[test]
326 fn test_create_query_context() {
327 let header = RequestHeader {
328 catalog: "cat-a-log".to_string(),
329 timezone: "+01:00".to_string(),
330 ..Default::default()
331 };
332 let query_context = create_query_context(
333 Channel::Unknown,
334 Some(&header),
335 vec![
336 ("auto_create_table".to_string(), "true".to_string()),
337 ("read_preference".to_string(), "leader".to_string()),
338 (
339 REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
340 "spoofed".to_string(),
341 ),
342 (
343 INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY.to_string(),
344 "spoofed-regs".to_string(),
345 ),
346 (
347 FLOW_SCHEDULED_TIME_MILLIS.to_string(),
348 "1700000000000".to_string(),
349 ),
350 ],
351 HashMap::from([(7, 88)]),
352 )
353 .unwrap();
354 assert_eq!(query_context.get_snapshot(7), Some(88));
355 assert_eq!(query_context.current_catalog(), "cat-a-log");
356 assert_eq!(query_context.current_schema(), DEFAULT_SCHEMA_NAME);
357 assert_eq!(
358 query_context.timezone(),
359 Timezone::Offset(FixedOffset::east_opt(3600).unwrap())
360 );
361 assert!(matches!(
362 query_context.read_preference(),
363 ReadPreference::Leader
364 ));
365 assert_eq!(query_context.extension("auto_create_table"), Some("true"));
366 assert_ne!(query_context.remote_query_id(), Some("spoofed"));
367 assert!(
368 query_context
369 .extension(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
370 .is_none()
371 );
372 assert_eq!(
373 query_context.extension(FLOW_SCHEDULED_TIME_MILLIS),
374 Some("1700000000000")
375 );
376 }
377
378 #[test]
379 fn test_create_query_context_ignores_remote_query_id_extension() {
380 let query_context = create_query_context(
381 Channel::Grpc,
382 None,
383 vec![(
384 REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
385 "spoofed-query-id".to_string(),
386 )],
387 HashMap::new(),
388 )
389 .unwrap();
390
391 assert_ne!(query_context.remote_query_id(), Some("spoofed-query-id"));
392 assert_eq!(
393 query_context.extension(REMOTE_QUERY_ID_EXTENSION_KEY),
394 query_context.remote_query_id()
395 );
396 }
397
398 #[test]
399 fn test_record_batch_error_to_status_preserves_error_details() {
400 let inner = InvalidParameterSnafu {
401 reason: "Column not found, column: new_col",
402 }
403 .build();
404 let err = Err::<(), _>(BoxedError::new(inner))
405 .context(ExecuteGrpcRequestSnafu)
406 .unwrap_err();
407
408 let status = Status::from(err);
409
410 assert_eq!(status.code(), Code::InvalidArgument);
411 assert!(
412 status
413 .message()
414 .contains("Column not found, column: new_col")
415 );
416 assert!(
417 status
418 .message()
419 .contains("Invalid request parameter: Column not found")
420 );
421 assert!(
422 status
423 .metadata()
424 .contains_key(GREPTIME_DB_HEADER_ERROR_CODE)
425 );
426 assert!(
427 status
428 .metadata()
429 .contains_key(GREPTIME_DB_HEADER_ERROR_RETRY_HINT)
430 );
431 }
432}