1use std::sync::Arc;
16
17use api::v1::SemanticType;
18use common_telemetry::{debug, error, tracing};
19use datafusion::common::ScalarValue;
20use datafusion::logical_expr::{self, Expr, Operator};
21use snafu::{OptionExt, ResultExt};
22use store_api::metadata::{RegionMetadataBuilder, RegionMetadataRef};
23use store_api::metric_engine_consts::DATA_SCHEMA_TABLE_ID_COLUMN_NAME;
24use store_api::region_engine::{RegionEngine, RegionScannerRef};
25use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
26
27use crate::engine::MetricEngineInner;
28use crate::error::{
29 InvalidMetadataSnafu, InvalidRequestSnafu, LogicalRegionNotFoundSnafu, MitoReadOperationSnafu,
30 Result,
31};
32use crate::metrics::MITO_OPERATION_ELAPSED;
33use crate::utils;
34
35impl MetricEngineInner {
36 #[tracing::instrument(skip_all)]
37 pub async fn read_region(
38 &self,
39 region_id: RegionId,
40 request: ScanRequest,
41 ) -> Result<RegionScannerRef> {
42 let is_reading_physical_region = self.is_physical_region(region_id);
43
44 if is_reading_physical_region {
45 debug!(
46 "Metric region received read request {request:?} on physical region {region_id:?}"
47 );
48 self.read_physical_region(region_id, request).await
49 } else {
50 self.read_logical_region(region_id, request).await
51 }
52 }
53
54 async fn read_physical_region(
56 &self,
57 region_id: RegionId,
58 request: ScanRequest,
59 ) -> Result<RegionScannerRef> {
60 let _timer = MITO_OPERATION_ELAPSED
61 .with_label_values(&["read_physical"])
62 .start_timer();
63
64 let logical_region = request
66 .filters
67 .iter()
68 .flat_map(logical_expr::utils::split_conjunction)
69 .any(|expr| {
70 let Expr::BinaryExpr(expr) = expr else {
71 return false;
72 };
73 expr.op == Operator::Eq
74 && matches!(
75 (expr.left.as_ref(), expr.right.as_ref()),
76 (Expr::Column(column), Expr::Literal(ScalarValue::UInt32(Some(_)), _))
77 | (Expr::Literal(ScalarValue::UInt32(Some(_)), _), Expr::Column(column))
78 if column.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME
79 )
80 });
81 let mut scanner = self
82 .mito
83 .handle_query(region_id, request)
84 .await
85 .context(MitoReadOperationSnafu)?;
86 scanner.set_logical_region(logical_region);
87
88 Ok(scanner)
89 }
90
91 async fn read_logical_region(
92 &self,
93 logical_region_id: RegionId,
94 request: ScanRequest,
95 ) -> Result<RegionScannerRef> {
96 let _timer = MITO_OPERATION_ELAPSED
97 .with_label_values(&["read"])
98 .start_timer();
99
100 let physical_region_id = self.get_physical_region_id(logical_region_id).await?;
101 let data_region_id = utils::to_data_region_id(physical_region_id);
102 let request = self
103 .transform_request(physical_region_id, logical_region_id, request)
104 .await?;
105 let mut scanner = self
106 .mito
107 .handle_query(data_region_id, request)
108 .await
109 .context(MitoReadOperationSnafu)?;
110 scanner.set_logical_region(true);
111 scanner.set_query_load_region_id(data_region_id);
112
113 Ok(scanner)
114 }
115
116 pub async fn get_last_seq_num(&self, region_id: RegionId) -> Result<SequenceNumber> {
117 let region_id = if self.is_physical_region(region_id) {
118 region_id
119 } else {
120 let physical_region_id = self.get_physical_region_id(region_id).await?;
121 utils::to_data_region_id(physical_region_id)
122 };
123 self.mito
124 .get_committed_sequence(region_id)
125 .await
126 .context(MitoReadOperationSnafu)
127 }
128
129 pub async fn load_region_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef> {
130 let is_reading_physical_region =
131 self.state.read().unwrap().exist_physical_region(region_id);
132
133 if is_reading_physical_region {
134 self.mito
135 .get_metadata(region_id)
136 .await
137 .context(MitoReadOperationSnafu)
138 } else {
139 let physical_region_id = self.get_physical_region_id(region_id).await?;
140 self.logical_region_metadata(physical_region_id, region_id)
141 .await
142 }
143 }
144
145 pub fn is_physical_region(&self, region_id: RegionId) -> bool {
147 self.state.read().unwrap().exist_physical_region(region_id)
148 }
149
150 async fn get_physical_region_id(&self, logical_region_id: RegionId) -> Result<RegionId> {
151 let state = &self.state.read().unwrap();
152 state
153 .get_physical_region_id(logical_region_id)
154 .with_context(|| {
155 error!("Trying to read an nonexistent region {logical_region_id}");
156 LogicalRegionNotFoundSnafu {
157 region_id: logical_region_id,
158 }
159 })
160 }
161
162 async fn transform_request(
164 &self,
165 physical_region_id: RegionId,
166 logical_region_id: RegionId,
167 mut request: ScanRequest,
168 ) -> Result<ScanRequest> {
169 let physical_projection = match request.projection.as_ref() {
171 Some(projection) => {
172 self.transform_projection(physical_region_id, logical_region_id, projection)
173 .await?
174 }
175 None => {
176 self.default_projection(physical_region_id, logical_region_id)
177 .await?
178 }
179 };
180
181 request.projection = Some(physical_projection);
184
185 request
186 .filters
187 .push(self.table_id_filter(logical_region_id));
188
189 Ok(request)
190 }
191
192 fn table_id_filter(&self, logical_region_id: RegionId) -> Expr {
194 logical_expr::col(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
195 .eq(logical_expr::lit(logical_region_id.table_id()))
196 }
197
198 pub async fn transform_projection(
202 &self,
203 physical_region_id: RegionId,
204 logical_region_id: RegionId,
205 origin_projection: &[usize],
206 ) -> Result<Vec<usize>> {
207 let all_logical_columns = self
209 .load_logical_column_names(physical_region_id, logical_region_id)
210 .await?;
211 let projected_logical_names = origin_projection
212 .iter()
213 .map(|&index| {
214 all_logical_columns
215 .get(index)
216 .map(String::as_str)
217 .with_context(|| InvalidRequestSnafu {
218 region_id: logical_region_id,
219 reason: format!("projection index {index} is out of bounds"),
220 })
221 })
222 .collect::<Result<Vec<_>>>()?;
223
224 let mut physical_projection = Vec::with_capacity(origin_projection.len());
226 let data_region_id = utils::to_data_region_id(physical_region_id);
227 let physical_metadata = self
228 .mito
229 .get_metadata(data_region_id)
230 .await
231 .context(MitoReadOperationSnafu)?;
232
233 for name in projected_logical_names {
234 physical_projection.push(physical_metadata.column_index_by_name(name).unwrap());
236 }
237
238 Ok(physical_projection)
239 }
240
241 pub async fn default_projection(
243 &self,
244 physical_region_id: RegionId,
245 logical_region_id: RegionId,
246 ) -> Result<Vec<usize>> {
247 let logical_columns = self
248 .load_logical_column_names(physical_region_id, logical_region_id)
249 .await?;
250 let mut projection = Vec::with_capacity(logical_columns.len());
251 let data_region_id = utils::to_data_region_id(physical_region_id);
252 let physical_metadata = self
253 .mito
254 .get_metadata(data_region_id)
255 .await
256 .context(MitoReadOperationSnafu)?;
257 for name in logical_columns {
258 projection.push(physical_metadata.column_index_by_name(&name).unwrap());
260 }
261
262 Ok(projection)
263 }
264
265 pub async fn logical_region_metadata(
266 &self,
267 physical_region_id: RegionId,
268 logical_region_id: RegionId,
269 ) -> Result<RegionMetadataRef> {
270 let logical_columns = self
271 .load_logical_columns(physical_region_id, logical_region_id)
272 .await?;
273
274 let primary_keys = logical_columns
275 .iter()
276 .filter_map(|col| {
277 if col.semantic_type == SemanticType::Tag {
278 Some(col.column_id)
279 } else {
280 None
281 }
282 })
283 .collect::<Vec<_>>();
284
285 let mut logical_metadata_builder = RegionMetadataBuilder::new(logical_region_id);
286 for col in logical_columns {
287 logical_metadata_builder.push_column_metadata(col);
288 }
289 logical_metadata_builder.primary_key(primary_keys);
290 let logical_metadata = logical_metadata_builder
291 .build()
292 .context(InvalidMetadataSnafu)?;
293
294 Ok(Arc::new(logical_metadata))
295 }
296}
297
298#[cfg(test)]
299impl MetricEngineInner {
300 pub async fn scan_to_stream(
301 &self,
302 region_id: RegionId,
303 request: ScanRequest,
304 ) -> Result<common_recordbatch::SendableRecordBatchStream, common_error::ext::BoxedError> {
305 let is_reading_physical_region = self.is_physical_region(region_id);
306
307 if is_reading_physical_region {
308 self.mito
309 .scan_to_stream(region_id, request)
310 .await
311 .map_err(common_error::ext::BoxedError::new)
312 } else {
313 let physical_region_id = self
314 .get_physical_region_id(region_id)
315 .await
316 .map_err(common_error::ext::BoxedError::new)?;
317 let request = self
318 .transform_request(physical_region_id, region_id, request)
319 .await
320 .map_err(common_error::ext::BoxedError::new)?;
321 self.mito
322 .scan_to_stream(physical_region_id, request)
323 .await
324 .map_err(common_error::ext::BoxedError::new)
325 }
326 }
327}
328
329#[cfg(test)]
330mod test {
331 use std::fmt;
332
333 use api::v1::Rows;
334 use common_error::ext::ErrorExt;
335 use common_error::status_code::StatusCode;
336 use datafusion::physical_plan::DisplayFormatType;
337 use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
338 use futures_util::TryStreamExt;
339 use futures_util::future::try_join_all;
340 use mito2::config::MitoConfig;
341 use store_api::region_engine::{PrepareRequest, QueryScanContext};
342 use store_api::region_request::{RegionFlushRequest, RegionPutRequest, RegionRequest};
343 use store_api::storage::{TimeSeriesDistribution, TimeSeriesRowSelector};
344
345 use super::*;
346 use crate::config::EngineConfig;
347 use crate::test_util::{
348 self, TestEnv, alter_logical_region_add_tag_columns, create_logical_region_request,
349 };
350
351 struct ScannerDisplay<'a>(&'a dyn store_api::region_engine::RegionScanner);
352
353 impl fmt::Display for ScannerDisplay<'_> {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 self.0.fmt_as(DisplayFormatType::Default, f)
356 }
357 }
358
359 async fn count_scanner_rows(scanner: &mut RegionScannerRef, partitions: usize) -> usize {
360 let ranges = scanner
361 .properties()
362 .partitions
363 .iter()
364 .flatten()
365 .copied()
366 .collect::<Vec<_>>();
367 let mut prepared = vec![Vec::new(); partitions];
368 prepared[0] = ranges;
369 scanner
370 .prepare(
371 PrepareRequest::default()
372 .with_ranges(prepared)
373 .with_target_partitions(partitions),
374 )
375 .unwrap();
376
377 let metrics = ExecutionPlanMetricsSet::default();
378 let context = QueryScanContext::default();
379 let streams = (0..partitions)
380 .map(|partition| {
381 scanner
382 .scan_partition(&context, &metrics, partition)
383 .unwrap()
384 })
385 .collect::<Vec<_>>();
386 try_join_all(streams.into_iter().map(|stream| async move {
387 stream
388 .try_fold(0, |rows, batch| async move { Ok(rows + batch.num_rows()) })
389 .await
390 }))
391 .await
392 .unwrap()
393 .into_iter()
394 .sum()
395 }
396
397 #[tokio::test]
398 async fn test_invalid_logical_projection() {
399 let env = TestEnv::new().await;
400 env.init_metric_region().await;
401
402 let logical_region_id = env.default_logical_region_id();
403 let invalid_index = usize::MAX;
404 let request = ScanRequest {
405 projection: Some(vec![invalid_index]),
406 ..Default::default()
407 };
408
409 let error =
410 match RegionEngine::handle_query(&env.metric(), logical_region_id, request).await {
411 Ok(_) => panic!("invalid logical projection unexpectedly succeeded"),
412 Err(error) => error,
413 };
414
415 assert_eq!(error.status_code(), StatusCode::InvalidArguments);
416 assert!(
417 error.to_string().contains(&format!(
418 "projection index {invalid_index} is out of bounds"
419 )),
420 "unexpected error: {error}"
421 );
422 }
423
424 #[tokio::test]
425 async fn test_single_logical_region_scan_properties() {
426 let env = TestEnv::new().await;
427 env.init_metric_region().await;
428
429 let physical_region_id = env.default_physical_region_id();
430 let logical_region_id = env.default_logical_region_id();
431 let table_id = logical_expr::col(DATA_SCHEMA_TABLE_ID_COLUMN_NAME);
432 let table_filter = table_id
433 .clone()
434 .eq(logical_expr::lit(logical_region_id.table_id()));
435 let job_filter = logical_expr::col("job").eq(logical_expr::lit("api"));
436 for (filters, logical_region) in [
437 (vec![], false),
438 (vec![table_filter.clone()], true),
439 (
440 vec![logical_expr::lit(logical_region_id.table_id()).eq(table_id.clone())],
441 true,
442 ),
443 (vec![job_filter.clone(), table_filter.clone()], true),
444 (vec![job_filter.clone().and(table_filter.clone())], true),
445 (
446 vec![
447 table_filter.clone().and(
448 job_filter
449 .clone()
450 .or(logical_expr::col("job").eq(logical_expr::lit("db"))),
451 ),
452 ],
453 true,
454 ),
455 (
456 vec![
457 table_filter
458 .clone()
459 .or(table_id.clone().eq(logical_expr::lit(4u32))),
460 ],
461 false,
462 ),
463 (vec![table_filter.clone().or(job_filter.clone())], false),
464 (
465 vec![table_id.clone().not_eq(logical_expr::lit(4u32))],
466 false,
467 ),
468 (vec![table_id.clone().gt(logical_expr::lit(4u32))], false),
469 (
470 vec![table_id.eq(logical_expr::lit(ScalarValue::UInt32(None)))],
471 false,
472 ),
473 (vec![job_filter], false),
474 ] {
475 let scanner = env
476 .metric()
477 .handle_query(
478 physical_region_id,
479 ScanRequest {
480 filters: filters.clone(),
481 distribution: Some(TimeSeriesDistribution::PerSeries),
482 ..Default::default()
483 },
484 )
485 .await
486 .unwrap();
487 assert_eq!(
488 scanner.properties().is_logical_region(),
489 logical_region,
490 "{filters:?}"
491 );
492 }
493
494 let scanner = env
495 .metric()
496 .handle_query(logical_region_id, ScanRequest::default())
497 .await
498 .unwrap();
499 assert!(scanner.properties().is_logical_region());
500
501 let scanner = env
503 .mito()
504 .handle_query(
505 utils::to_data_region_id(physical_region_id),
506 ScanRequest {
507 filters: vec![table_filter],
508 distribution: Some(TimeSeriesDistribution::PerSeries),
509 ..Default::default()
510 },
511 )
512 .await
513 .unwrap();
514 assert!(!scanner.properties().is_logical_region());
515 }
516
517 #[tokio::test]
518 async fn test_transform_scan_req() {
519 let env = TestEnv::new().await;
520 env.init_metric_region().await;
521
522 let logical_region_id = env.default_logical_region_id();
523 let physical_region_id = env.default_physical_region_id();
524
525 let logical_region_id2 = RegionId::new(1112345678, 999);
527 let create_request =
528 create_logical_region_request(&["123", "456", "789"], physical_region_id, "blabla");
529 env.metric()
530 .handle_request(logical_region_id2, RegionRequest::Create(create_request))
531 .await
532 .unwrap();
533
534 let alter_request =
536 alter_logical_region_add_tag_columns(123456, &["987", "798", "654", "321"]);
537 env.metric()
538 .handle_request(logical_region_id, RegionRequest::Alter(alter_request))
539 .await
540 .unwrap();
541
542 let projection = Some(vec![0, 1, 2, 3, 4, 5, 6]);
544 let scan_req = ScanRequest {
545 projection,
546 filters: vec![],
547 series_row_selector: Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
548 ..Default::default()
549 };
550
551 let scan_req = env
552 .metric()
553 .inner
554 .transform_request(physical_region_id, logical_region_id, scan_req)
555 .await
556 .unwrap();
557
558 assert_eq!(
559 scan_req.projection.as_deref().unwrap(),
560 &[11, 10, 9, 8, 0, 1, 4]
561 );
562 assert_eq!(scan_req.filters.len(), 1);
563 assert_eq!(
564 scan_req.series_row_selector,
565 Some(TimeSeriesRowSelector::LastRow { after_merge: true })
566 );
567 assert_eq!(
568 scan_req.filters[0],
569 logical_expr::col(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
570 .eq(logical_expr::lit(logical_region_id.table_id()))
571 );
572
573 let scan_req = ScanRequest::default();
575 let scan_req = env
576 .metric()
577 .inner
578 .transform_request(physical_region_id, logical_region_id, scan_req)
579 .await
580 .unwrap();
581 assert_eq!(
582 scan_req.projection.as_deref().unwrap(),
583 &[11, 10, 9, 8, 0, 1, 4]
584 );
585 }
586
587 #[tokio::test]
588 async fn test_two_phase_series_scan_reads_metric_region() {
589 let env = TestEnv::with_mito_config(
590 "test_two_phase_series_scan",
591 MitoConfig {
592 experimental_series_scan_v2: true,
593 ..Default::default()
594 },
595 EngineConfig::default(),
596 )
597 .await;
598 env.init_metric_region().await;
599
600 let physical_region_id = env.default_physical_region_id();
601 let logical_region_id = env.default_logical_region_id();
602 let logical_region_id_2 = RegionId::new(1024, logical_region_id.region_number());
603 env.metric()
604 .handle_request(
605 logical_region_id_2,
606 RegionRequest::Create(create_logical_region_request(
607 &["job"],
608 physical_region_id,
609 "test_metric_region_2",
610 )),
611 )
612 .await
613 .unwrap();
614
615 let schema = test_util::row_schema_with_tags(&["job"]);
616 let put = |rows| {
617 RegionRequest::Put(RegionPutRequest {
618 skip_wal: false,
619 rows: Rows {
620 schema: schema.clone(),
621 rows: test_util::build_rows(1, rows),
622 },
623 hint: None,
624 partition_expr_version: None,
625 })
626 };
627 env.metric()
628 .handle_request(logical_region_id, put(3))
629 .await
630 .unwrap();
631 env.metric()
632 .handle_request(
633 physical_region_id,
634 RegionRequest::Flush(RegionFlushRequest::default()),
635 )
636 .await
637 .unwrap();
638 env.metric()
639 .handle_request(logical_region_id, put(2))
640 .await
641 .unwrap();
642 env.metric()
643 .handle_request(logical_region_id_2, put(4))
644 .await
645 .unwrap();
646
647 let data_region_id = utils::to_data_region_id(physical_region_id);
648 let request = ScanRequest {
649 distribution: Some(TimeSeriesDistribution::PerSeries),
650 ..Default::default()
651 };
652 let mut scanner = env
653 .mito()
654 .handle_query(data_region_id, request)
655 .await
656 .unwrap();
657
658 let rows = tokio::time::timeout(
659 std::time::Duration::from_secs(30),
660 count_scanner_rows(&mut scanner, 2),
661 )
662 .await
663 .expect("two-phase series scan should not deadlock");
664 assert_eq!(7, rows);
665
666 let explain = ScannerDisplay(scanner.as_ref()).to_string();
667 assert!(explain.contains("\"mode\":\"two_phase\""), "{explain}");
668 }
669
670 #[tokio::test]
671 async fn test_series_scan_v2_flag_disables_two_phase_mode() {
672 let env = TestEnv::with_mito_config(
673 "test_disable_two_phase_series_scan",
674 MitoConfig {
675 experimental_series_scan_v2: false,
676 ..Default::default()
677 },
678 EngineConfig::default(),
679 )
680 .await;
681 env.init_metric_region().await;
682
683 let data_region_id = utils::to_data_region_id(env.default_physical_region_id());
684 let scanner = env
685 .mito()
686 .handle_query(
687 data_region_id,
688 ScanRequest {
689 distribution: Some(TimeSeriesDistribution::PerSeries),
690 ..Default::default()
691 },
692 )
693 .await
694 .unwrap();
695
696 let explain = ScannerDisplay(scanner.as_ref()).to_string();
697 assert!(explain.contains("\"mode\":\"legacy\""), "{explain}");
698 }
699}