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::{INSERT_SKIP_WAL_HINT, 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 channel: Channel,
82 ) -> Result<Output> {
83 let header = request.header.as_ref();
84 let hints = crate::hint_headers::validate_public_hints(hints)?;
85 let query_ctx = create_query_context(channel, header, hints, HashMap::new())?;
86 let query = request.request.context(InvalidQuerySnafu {
87 reason: "Expecting non-empty GreptimeRequest.",
88 })?;
89 self.authenticate_request_with_query_ctx(request.header.as_ref(), &query_ctx)
90 .await?;
91 self.handle_request_with_query_ctx(query, query_ctx).await
92 }
93
94 pub(crate) async fn authenticate_request_with_query_ctx(
95 &self,
96 header: Option<&RequestHeader>,
97 query_ctx: &QueryContextRef,
98 ) -> Result<()> {
99 let user_info = context_auth::auth(self.user_provider.clone(), header, query_ctx).await?;
100 query_ctx.set_current_user(user_info);
101 Ok(())
102 }
103
104 pub(crate) fn handle_request_with_query_ctx(
105 &self,
106 query: QueryRequest,
107 query_ctx: QueryContextRef,
108 ) -> impl Future<Output = Result<Output>> + Send + 'static {
109 let handler = self.handler.clone();
110 let runtime = self.runtime.clone();
111 let request_type = request_type(&query).to_string();
112 let db = query_ctx.get_db_string();
113 let timer = RequestTimer::new(db.clone(), request_type);
114 let tracing_context = TracingContext::from_current_span();
115
116 async move {
117 let result_future = async move {
118 handler
119 .do_query(query, query_ctx)
120 .trace(tracing_context.attach(tracing::info_span!(
121 "GreptimeRequestHandler::handle_request_runtime"
122 )))
123 .await
124 .map_err(|e| {
125 if e.status_code().should_log_error() {
126 let root_error = e.root_cause().unwrap_or(&e);
127 error!(e; "Failed to handle request, error: {}", root_error.to_string());
128 } else {
129 debug!("Failed to handle request, err: {:?}", e);
131 }
132 e
133 })
134 };
135
136 match runtime {
137 Some(runtime) => {
138 runtime
146 .spawn(result_future)
147 .await
148 .context(JoinTaskSnafu)
149 .inspect_err(|e| {
150 timer.record(e.status_code());
151 })?
152 }
153 None => result_future.await,
154 }
155 }
156 }
157
158 pub(crate) async fn put_record_batches(
159 &self,
160 stream: PutRecordBatchRequestStream,
161 result_sender: mpsc::Sender<TonicResult<DoPutResponse>>,
162 query_ctx: QueryContextRef,
163 ) {
164 let handler = self.handler.clone();
165 let runtime = self
166 .runtime
167 .clone()
168 .unwrap_or_else(common_runtime::global_runtime);
169 runtime.spawn(async move {
170 let mut result_stream = handler.handle_put_record_batch_stream(stream, query_ctx);
171
172 while let Some(result) = result_stream.next().await {
173 match &result {
174 Ok(response) => {
175 metrics::GRPC_BULK_INSERT_ELAPSED.observe(response.elapsed_secs());
177 }
178 Err(e) => {
179 error!(e; "Failed to handle flight record batches");
180 }
181 }
182
183 if let Err(e) = result_sender.try_send(result.map_err(Status::from))
184 && let TrySendError::Closed(_) = e
185 {
186 warn!(r#""DoPut" client maybe unreachable, abort handling its message"#);
187 break;
188 }
189 }
190 });
191 }
192}
193
194pub fn get_request_type(request: &GreptimeRequest) -> &'static str {
195 request
196 .request
197 .as_ref()
198 .map(request_type)
199 .unwrap_or_default()
200}
201
202pub(crate) fn create_query_context(
205 channel: Channel,
206 header: Option<&RequestHeader>,
207 extensions: Vec<(String, String)>,
208 snapshot_seqs: HashMap<u64, u64>,
209) -> Result<QueryContextRef> {
210 let (catalog, schema) = header
211 .map(|header| {
212 if !header.dbname.is_empty() {
215 parse_catalog_and_schema_from_db_string(&header.dbname)
216 } else {
217 (
218 if !header.catalog.is_empty() {
219 header.catalog.to_lowercase()
220 } else {
221 DEFAULT_CATALOG_NAME.to_string()
222 },
223 if !header.schema.is_empty() {
224 header.schema.to_lowercase()
225 } else {
226 DEFAULT_SCHEMA_NAME.to_string()
227 },
228 )
229 }
230 })
231 .unwrap_or_else(|| {
232 (
233 DEFAULT_CATALOG_NAME.to_string(),
234 DEFAULT_SCHEMA_NAME.to_string(),
235 )
236 });
237 let timezone = parse_timezone(header.map(|h| h.timezone.as_str()));
238 let mut ctx_builder = QueryContextBuilder::default()
239 .current_catalog(catalog)
240 .current_schema(schema)
241 .timezone(timezone)
242 .channel(channel)
243 .snapshot_seqs(Arc::new(RwLock::new(snapshot_seqs)));
244
245 for (key, value) in extensions {
246 match key.as_str() {
247 READ_PREFERENCE_HINT => {
248 let Ok(read_preference) = ReadPreference::from_str(&value) else {
249 return UnknownHintSnafu {
250 hint: format!("{key}={value}"),
251 }
252 .fail();
253 };
254 ctx_builder = ctx_builder.read_preference(read_preference);
255 }
256 INSERT_SKIP_WAL_HINT => {
257 let skip_wal = value.parse::<bool>().map_err(|_| {
258 UnknownHintSnafu {
259 hint: format!("{key}={value}"),
260 }
261 .build()
262 })?;
263 ctx_builder = ctx_builder.skip_wal(skip_wal);
264 }
265 _ if is_reserved_extension_key(&key) => {
266 debug!(
267 key = key.as_str(),
268 "Ignoring reserved external query context extension key"
269 );
270 }
271 _ => {
272 ctx_builder = ctx_builder.set_extension(key, value);
273 }
274 }
275 }
276 Ok(ctx_builder.build().into())
277}
278
279pub(crate) struct RequestTimer {
283 start: Instant,
284 db: String,
285 request_type: String,
286 status_code: StatusCode,
287}
288
289impl RequestTimer {
290 pub fn new(db: String, request_type: String) -> RequestTimer {
292 RequestTimer {
293 start: Instant::now(),
294 db,
295 request_type,
296 status_code: StatusCode::Success,
297 }
298 }
299
300 pub fn record(mut self, status_code: StatusCode) {
302 self.status_code = status_code;
303 }
304}
305
306impl Drop for RequestTimer {
307 fn drop(&mut self) {
308 METRIC_SERVER_GRPC_DB_REQUEST_TIMER
309 .with_label_values(&[
310 self.db.as_str(),
311 self.request_type.as_str(),
312 self.status_code.as_ref(),
313 ])
314 .observe(self.start.elapsed().as_secs_f64());
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use chrono::FixedOffset;
321 use common_error::ext::BoxedError;
322 use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT};
323 use common_time::Timezone;
324 use query::options::FLOW_SCHEDULED_TIME_MILLIS;
325 use session::hints::{
326 INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY, READ_PREFERENCE_EXTENSION_KEY,
327 REMOTE_QUERY_ID_EXTENSION_KEY,
328 };
329 use snafu::ResultExt;
330 use tonic::Code;
331
332 use super::*;
333 use crate::error::{ExecuteGrpcRequestSnafu, InvalidParameterSnafu};
334
335 #[test]
336 fn test_create_query_context_typed_skip_wal() {
337 let ctx = create_query_context(Channel::Grpc, None, vec![], HashMap::new()).unwrap();
338 assert!(!ctx.skip_wal());
339 let legacy = create_query_context(
340 Channel::Grpc,
341 None,
342 vec![("skip_wal".to_string(), "true".to_string())],
343 HashMap::new(),
344 )
345 .unwrap();
346 assert!(!legacy.skip_wal());
347 assert_eq!(legacy.extension("skip_wal"), Some("true"));
348 for (value, expected) in [("true", true), ("false", false)] {
349 let ctx = create_query_context(
350 Channel::Grpc,
351 None,
352 vec![(INSERT_SKIP_WAL_HINT.to_string(), value.to_string())],
353 HashMap::new(),
354 )
355 .unwrap();
356 assert_eq!(ctx.skip_wal(), expected);
357 assert_eq!(ctx.extension(INSERT_SKIP_WAL_HINT), None);
358 }
359 for value in ["", "TRUE", "1", "invalid"] {
360 assert!(
361 create_query_context(
362 Channel::Grpc,
363 None,
364 vec![(INSERT_SKIP_WAL_HINT.to_string(), value.to_string())],
365 HashMap::new()
366 )
367 .is_err()
368 );
369 }
370 let ctx = create_query_context(
371 Channel::Grpc,
372 None,
373 vec![
374 (INSERT_SKIP_WAL_HINT.to_string(), "true".to_string()),
375 (INSERT_SKIP_WAL_HINT.to_string(), "false".to_string()),
376 ],
377 HashMap::new(),
378 )
379 .unwrap();
380 assert!(!ctx.skip_wal());
381 assert_eq!(ctx.extension(INSERT_SKIP_WAL_HINT), None);
382 }
383
384 #[test]
385 fn test_create_query_context_read_preference_duplicates() {
386 for (values, valid) in [
387 (["leader", "LEADER"], true),
388 (["invalid", "leader"], false),
389 (["leader", "invalid"], false),
390 ] {
391 let result = create_query_context(
392 Channel::Grpc,
393 None,
394 values
395 .into_iter()
396 .map(|value| (READ_PREFERENCE_HINT.to_string(), value.to_string()))
397 .collect(),
398 HashMap::new(),
399 );
400 if valid {
401 let ctx = result.unwrap();
402 assert!(matches!(ctx.read_preference(), ReadPreference::Leader));
403 assert_eq!(ctx.extension(READ_PREFERENCE_HINT), None);
404 } else {
405 assert!(result.is_err());
406 }
407 }
408 }
409
410 #[test]
411 fn test_create_query_context() {
412 let header = RequestHeader {
413 catalog: "cat-a-log".to_string(),
414 timezone: "+01:00".to_string(),
415 ..Default::default()
416 };
417 let query_context = create_query_context(
418 Channel::Unknown,
419 Some(&header),
420 vec![
421 ("auto_create_table".to_string(), "true".to_string()),
422 ("read_preference".to_string(), "leader".to_string()),
423 (
424 READ_PREFERENCE_EXTENSION_KEY.to_string(),
425 "follower".to_string(),
426 ),
427 (
428 REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
429 "spoofed".to_string(),
430 ),
431 (
432 INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY.to_string(),
433 "spoofed-regs".to_string(),
434 ),
435 (
436 FLOW_SCHEDULED_TIME_MILLIS.to_string(),
437 "1700000000000".to_string(),
438 ),
439 ],
440 HashMap::from([(7, 88)]),
441 )
442 .unwrap();
443 assert_eq!(query_context.get_snapshot(7), Some(88));
444 assert_eq!(query_context.current_catalog(), "cat-a-log");
445 assert_eq!(query_context.current_schema(), DEFAULT_SCHEMA_NAME);
446 assert_eq!(
447 query_context.timezone(),
448 Timezone::Offset(FixedOffset::east_opt(3600).unwrap())
449 );
450 assert!(matches!(
451 query_context.read_preference(),
452 ReadPreference::Leader
453 ));
454 assert_eq!(query_context.extension("auto_create_table"), Some("true"));
455 assert_eq!(query_context.extension(READ_PREFERENCE_HINT), None);
456 assert_eq!(query_context.extension(READ_PREFERENCE_EXTENSION_KEY), None);
457 assert_ne!(query_context.remote_query_id(), Some("spoofed"));
458 assert!(
459 query_context
460 .extension(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
461 .is_none()
462 );
463 assert_eq!(
464 query_context.extension(FLOW_SCHEDULED_TIME_MILLIS),
465 Some("1700000000000")
466 );
467 }
468
469 #[test]
470 fn test_create_query_context_ignores_remote_query_id_extension() {
471 let query_context = create_query_context(
472 Channel::Grpc,
473 None,
474 vec![(
475 REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
476 "spoofed-query-id".to_string(),
477 )],
478 HashMap::new(),
479 )
480 .unwrap();
481
482 assert_ne!(query_context.remote_query_id(), Some("spoofed-query-id"));
483 assert_eq!(
484 query_context.extension(REMOTE_QUERY_ID_EXTENSION_KEY),
485 query_context.remote_query_id()
486 );
487 }
488
489 #[test]
490 fn test_record_batch_error_to_status_preserves_error_details() {
491 let inner = InvalidParameterSnafu {
492 reason: "Column not found, column: new_col",
493 }
494 .build();
495 let err = Err::<(), _>(BoxedError::new(inner))
496 .context(ExecuteGrpcRequestSnafu)
497 .unwrap_err();
498
499 let status = Status::from(err);
500
501 assert_eq!(status.code(), Code::InvalidArgument);
502 assert!(
503 status
504 .message()
505 .contains("Column not found, column: new_col")
506 );
507 assert!(
508 status
509 .message()
510 .contains("Invalid request parameter: Column not found")
511 );
512 assert!(
513 status
514 .metadata()
515 .contains_key(GREPTIME_DB_HEADER_ERROR_CODE)
516 );
517 assert!(
518 status
519 .metadata()
520 .contains_key(GREPTIME_DB_HEADER_ERROR_RETRY_HINT)
521 );
522 }
523}