1use std::collections::hash_map::Entry;
16use std::collections::{BTreeMap, HashMap};
17use std::sync::{Arc, Mutex, Weak};
18use std::time::Duration;
19
20use api::v1::helper::row;
21use api::v1::value::ValueData;
22use api::v1::{ColumnDataType, ColumnSchema, Rows, SemanticType};
23use async_stream::try_stream;
24use base64::Engine;
25use base64::engine::general_purpose::STANDARD_NO_PAD;
26use common_base::readable_size::ReadableSize;
27use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
28use common_telemetry::{debug, info, warn};
29use datafusion::prelude::{col, lit};
30use futures_util::TryStreamExt;
31use futures_util::stream::BoxStream;
32use mito2::engine::MitoEngine;
33use moka::future::Cache;
34use moka::policy::EvictionPolicy;
35use snafu::{OptionExt, ResultExt};
36use store_api::metadata::ColumnMetadata;
37use store_api::metric_engine_consts::{
38 METADATA_SCHEMA_KEY_COLUMN_INDEX, METADATA_SCHEMA_KEY_COLUMN_NAME,
39 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME, METADATA_SCHEMA_VALUE_COLUMN_INDEX,
40 METADATA_SCHEMA_VALUE_COLUMN_NAME,
41};
42use store_api::region_engine::RegionEngine;
43use store_api::region_request::{RegionDeleteRequest, RegionPutRequest, RegionRequest};
44use store_api::storage::{RegionId, ScanRequest};
45use tokio::sync::{
46 OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
47};
48
49use crate::error::{
50 CacheGetSnafu, CollectRecordBatchStreamSnafu, DecodeColumnValueSnafu,
51 DeserializeColumnMetadataSnafu, LogicalRegionNotFoundSnafu, MitoReadOperationSnafu,
52 MitoWriteOperationSnafu, ParseRegionIdSnafu, Result,
53};
54use crate::utils;
55
56const REGION_PREFIX: &str = "__region_";
57const COLUMN_PREFIX: &str = "__column_";
58
59pub struct MetadataRegion {
72 pub(crate) mito: MitoEngine,
73 cache: Cache<RegionId, RegionMetadataCacheEntry>,
78 cache_access_locks: CacheAccessLockRegistry,
85 logical_region_lock: RwLock<HashMap<RegionId, Arc<RwLock<()>>>>,
90}
91
92#[derive(Clone)]
93struct RegionMetadataCacheEntry {
94 key_values: Arc<BTreeMap<String, String>>,
95 size: usize,
96}
97
98type CacheAccessLockRegistry = Arc<Mutex<HashMap<RegionId, Weak<RwLock<()>>>>>;
102
103struct CacheAccessLockLease {
107 registry: CacheAccessLockRegistry,
108 region_id: RegionId,
109 lock: Option<Arc<RwLock<()>>>,
113}
114
115impl CacheAccessLockLease {
116 async fn read(&self) -> RwLockReadGuard<'_, ()> {
117 self.lock().read().await
118 }
119
120 async fn write(&self) -> RwLockWriteGuard<'_, ()> {
121 self.lock().write().await
122 }
123
124 fn lock(&self) -> &RwLock<()> {
125 self.lock
126 .as_deref()
127 .expect("cache access lock lease must hold a lock")
131 }
132}
133
134impl Drop for CacheAccessLockLease {
135 fn drop(&mut self) {
136 let Some(lock) = self.lock.take() else {
137 return;
138 };
139 let weak = Arc::downgrade(&lock);
140 drop(lock);
143
144 let mut registry = self
145 .registry
146 .lock()
147 .unwrap_or_else(|poisoned| poisoned.into_inner());
148 let Some(current) = registry.get(&self.region_id) else {
149 return;
150 };
151 if current.ptr_eq(&weak) && current.upgrade().is_none() {
153 registry.remove(&self.region_id);
154 }
155 }
156}
157
158const MAX_CACHE_SIZE: u64 = ReadableSize::mb(128).as_bytes();
160const CACHE_TTL: Duration = Duration::from_secs(5 * 60);
162
163impl MetadataRegion {
164 pub fn new(mito: MitoEngine) -> Self {
165 let cache = Cache::builder()
166 .max_capacity(MAX_CACHE_SIZE)
167 .eviction_policy(EvictionPolicy::lru())
170 .time_to_live(CACHE_TTL)
171 .weigher(|_, v: &RegionMetadataCacheEntry| v.size as u32)
172 .build();
173 Self {
174 mito,
175 cache,
176 cache_access_locks: CacheAccessLockRegistry::default(),
177 logical_region_lock: RwLock::new(HashMap::new()),
178 }
179 }
180
181 pub async fn open_logical_region(&self, logical_region_id: RegionId) -> bool {
185 match self
186 .logical_region_lock
187 .write()
188 .await
189 .entry(logical_region_id)
190 {
191 Entry::Occupied(_) => false,
192 Entry::Vacant(vacant_entry) => {
193 vacant_entry.insert(Arc::new(RwLock::new(())));
194 true
195 }
196 }
197 }
198
199 pub async fn read_lock_logical_region(
201 &self,
202 logical_region_id: RegionId,
203 ) -> Result<OwnedRwLockReadGuard<()>> {
204 let lock = self
205 .logical_region_lock
206 .read()
207 .await
208 .get(&logical_region_id)
209 .context(LogicalRegionNotFoundSnafu {
210 region_id: logical_region_id,
211 })?
212 .clone();
213 Ok(RwLock::read_owned(lock).await)
214 }
215
216 pub async fn write_lock_logical_region(
218 &self,
219 logical_region_id: RegionId,
220 ) -> Result<OwnedRwLockWriteGuard<()>> {
221 let lock = self
222 .logical_region_lock
223 .read()
224 .await
225 .get(&logical_region_id)
226 .context(LogicalRegionNotFoundSnafu {
227 region_id: logical_region_id,
228 })?
229 .clone();
230 Ok(RwLock::write_owned(lock).await)
231 }
232
233 pub async fn remove_logical_region(
237 &self,
238 physical_region_id: RegionId,
239 logical_region_id: RegionId,
240 ) -> Result<()> {
241 let region_id = utils::to_metadata_region_id(physical_region_id);
243 let region_key = Self::concat_region_key(logical_region_id);
244
245 let logical_columns = self
247 .logical_columns(physical_region_id, logical_region_id)
248 .await?;
249 let mut column_keys = logical_columns
250 .into_iter()
251 .map(|(col, _)| Self::concat_column_key(logical_region_id, &col))
252 .collect::<Vec<_>>();
253
254 column_keys.push(region_key);
256 self.delete(region_id, &column_keys).await?;
257
258 self.logical_region_lock
259 .write()
260 .await
261 .remove(&logical_region_id);
262
263 Ok(())
264 }
265
266 pub async fn logical_columns(
270 &self,
271 physical_region_id: RegionId,
272 logical_region_id: RegionId,
273 ) -> Result<Vec<(String, ColumnMetadata)>> {
274 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
275 let region_column_prefix = Self::concat_column_key_prefix(logical_region_id);
276
277 let mut columns = vec![];
278 for (k, v) in self
279 .get_all_with_prefix(metadata_region_id, ®ion_column_prefix)
280 .await?
281 {
282 if !k.starts_with(®ion_column_prefix) {
283 continue;
284 }
285 let (_, column_name) = Self::parse_column_key(&k)?.unwrap();
287 let column_metadata = Self::deserialize_column_metadata(&v)?;
288 columns.push((column_name, column_metadata));
289 }
290
291 Ok(columns)
292 }
293
294 pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result<Vec<RegionId>> {
296 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
297
298 let mut regions = vec![];
299 for k in self
300 .get_all_key_with_prefix(metadata_region_id, REGION_PREFIX)
301 .await?
302 {
303 if !k.starts_with(REGION_PREFIX) {
304 continue;
305 }
306 let region_id = Self::parse_region_key(&k).unwrap();
308 let region_id = region_id.parse::<u64>().unwrap().into();
309 regions.push(region_id);
310 }
311
312 Ok(regions)
313 }
314}
315
316impl MetadataRegion {
318 pub fn concat_region_key(region_id: RegionId) -> String {
319 format!("{REGION_PREFIX}{}", region_id.as_u64())
320 }
321
322 pub fn concat_column_key(region_id: RegionId, column_name: &str) -> String {
324 let encoded_column_name = STANDARD_NO_PAD.encode(column_name);
325 format!(
326 "{COLUMN_PREFIX}{}_{}",
327 region_id.as_u64(),
328 encoded_column_name
329 )
330 }
331
332 pub fn concat_column_key_prefix(region_id: RegionId) -> String {
334 format!("{COLUMN_PREFIX}{}_", region_id.as_u64())
335 }
336
337 pub fn parse_region_key(key: &str) -> Option<&str> {
338 key.strip_prefix(REGION_PREFIX)
339 }
340
341 pub fn parse_column_key(key: &str) -> Result<Option<(RegionId, String)>> {
343 if let Some(stripped) = key.strip_prefix(COLUMN_PREFIX) {
344 let mut iter = stripped.split('_');
345
346 let region_id_raw = iter.next().unwrap();
347 let region_id = region_id_raw
348 .parse::<u64>()
349 .with_context(|_| ParseRegionIdSnafu { raw: region_id_raw })?
350 .into();
351
352 let encoded_column_name = iter.next().unwrap();
353 let column_name = STANDARD_NO_PAD
354 .decode(encoded_column_name)
355 .context(DecodeColumnValueSnafu)?;
356
357 Ok(Some((region_id, String::from_utf8(column_name).unwrap())))
358 } else {
359 Ok(None)
360 }
361 }
362
363 pub fn serialize_column_metadata(column_metadata: &ColumnMetadata) -> String {
364 serde_json::to_string(column_metadata).unwrap()
365 }
366
367 pub fn deserialize_column_metadata(column_metadata: &str) -> Result<ColumnMetadata> {
368 serde_json::from_str(column_metadata).with_context(|_| DeserializeColumnMetadataSnafu {
369 raw: column_metadata,
370 })
371 }
372}
373
374pub fn decode_batch_stream<T: Send + 'static>(
376 mut record_batch_stream: SendableRecordBatchStream,
377 decode: fn(RecordBatch) -> Vec<T>,
378) -> BoxStream<'static, Result<T>> {
379 let stream = try_stream! {
380 while let Some(batch) = record_batch_stream.try_next().await.context(CollectRecordBatchStreamSnafu)? {
381 for item in decode(batch) {
382 yield item;
383 }
384 }
385 };
386 Box::pin(stream)
387}
388
389fn decode_record_batch_to_key_and_value(batch: RecordBatch) -> Vec<(String, String)> {
391 let keys = batch.iter_column_as_string(0);
392 let values = batch.iter_column_as_string(1);
393 keys.zip(values)
394 .filter_map(|(k, v)| match (k, v) {
395 (Some(k), Some(v)) => Some((k, v)),
396 (Some(k), None) => Some((k, "".to_string())),
397 (None, _) => None,
398 })
399 .collect::<Vec<_>>()
400}
401
402fn decode_record_batch_to_key(batch: RecordBatch) -> Vec<String> {
404 batch.iter_column_as_string(0).flatten().collect::<Vec<_>>()
405}
406
407impl MetadataRegion {
411 fn build_prefix_read_request(prefix: &str, key_only: bool) -> ScanRequest {
412 let filter_expr = col(METADATA_SCHEMA_KEY_COLUMN_NAME).like(lit(prefix));
413
414 let projection = if key_only {
415 vec![METADATA_SCHEMA_KEY_COLUMN_INDEX]
416 } else {
417 vec![
418 METADATA_SCHEMA_KEY_COLUMN_INDEX,
419 METADATA_SCHEMA_VALUE_COLUMN_INDEX,
420 ]
421 };
422 ScanRequest {
423 projection: Some(projection),
424 filters: vec![filter_expr],
425 ..Default::default()
426 }
427 }
428
429 fn build_read_request() -> ScanRequest {
430 let projection = vec![
431 METADATA_SCHEMA_KEY_COLUMN_INDEX,
432 METADATA_SCHEMA_VALUE_COLUMN_INDEX,
433 ];
434 ScanRequest {
435 projection: Some(projection),
436 ..Default::default()
437 }
438 }
439
440 async fn load_all(&self, metadata_region_id: RegionId) -> Result<RegionMetadataCacheEntry> {
441 let scan_req = MetadataRegion::build_read_request();
442 let record_batch_stream = self
443 .mito
444 .scan_to_stream(metadata_region_id, scan_req)
445 .await
446 .context(MitoReadOperationSnafu)?;
447
448 let kv = decode_batch_stream(record_batch_stream, decode_record_batch_to_key_and_value)
449 .try_collect::<BTreeMap<_, _>>()
450 .await?;
451 let mut size = 0;
452 for (k, v) in kv.iter() {
453 size += k.len();
454 size += v.len();
455 }
456 let kv = Arc::new(kv);
457 Ok(RegionMetadataCacheEntry {
458 key_values: kv,
459 size,
460 })
461 }
462
463 fn cache_access_lock(&self, metadata_region_id: RegionId) -> CacheAccessLockLease {
467 let mut registry = self
468 .cache_access_locks
469 .lock()
470 .unwrap_or_else(|poisoned| poisoned.into_inner());
471 let lock = match registry.get(&metadata_region_id).and_then(Weak::upgrade) {
472 Some(lock) => lock,
473 None => {
474 let lock = Arc::new(RwLock::new(()));
475 registry.insert(metadata_region_id, Arc::downgrade(&lock));
476 lock
477 }
478 };
479
480 CacheAccessLockLease {
481 registry: Arc::clone(&self.cache_access_locks),
482 region_id: metadata_region_id,
483 lock: Some(lock),
484 }
485 }
486
487 async fn get_all_with_prefix(
488 &self,
489 metadata_region_id: RegionId,
490 prefix: &str,
491 ) -> Result<HashMap<String, String>> {
492 let cache_access_lock = self.cache_access_lock(metadata_region_id);
493 let _cache_guard = cache_access_lock.read().await;
494 let region_metadata = self
495 .cache
496 .try_get_with(metadata_region_id, self.load_all(metadata_region_id))
497 .await
498 .context(CacheGetSnafu)?;
499
500 let mut result = HashMap::new();
501 get_all_with_prefix(®ion_metadata, prefix, |k, v| {
502 result.insert(k.to_string(), v.to_string());
503 Ok(())
504 })?;
505 Ok(result)
506 }
507
508 pub async fn get_all_key_with_prefix(
509 &self,
510 region_id: RegionId,
511 prefix: &str,
512 ) -> Result<Vec<String>> {
513 let scan_req = MetadataRegion::build_prefix_read_request(prefix, true);
514 let record_batch_stream = self
515 .mito
516 .scan_to_stream(region_id, scan_req)
517 .await
518 .context(MitoReadOperationSnafu)?;
519
520 decode_batch_stream(record_batch_stream, decode_record_batch_to_key)
521 .try_collect::<Vec<_>>()
522 .await
523 }
524
525 async fn delete(&self, metadata_region_id: RegionId, keys: &[String]) -> Result<()> {
528 let delete_request = Self::build_delete_request(keys);
529 self.write_metadata(metadata_region_id, RegionRequest::Delete(delete_request))
530 .await
531 }
532
533 async fn write_metadata(
535 &self,
536 metadata_region_id: RegionId,
537 request: RegionRequest,
538 ) -> Result<()> {
539 let cache_access_lock = self.cache_access_lock(metadata_region_id);
540 let _cache_guard = cache_access_lock.write().await;
541 let result = self
542 .mito
543 .handle_request(metadata_region_id, request)
544 .await
545 .context(MitoWriteOperationSnafu);
546 self.cache.invalidate(&metadata_region_id).await;
549 result?;
550
551 Ok(())
552 }
553
554 pub(crate) fn build_put_request_from_iter(
555 kv: impl Iterator<Item = (String, String)>,
556 ) -> RegionPutRequest {
557 let cols = vec![
558 ColumnSchema {
559 column_name: METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME.to_string(),
560 datatype: ColumnDataType::TimestampMillisecond as _,
561 semantic_type: SemanticType::Timestamp as _,
562 ..Default::default()
563 },
564 ColumnSchema {
565 column_name: METADATA_SCHEMA_KEY_COLUMN_NAME.to_string(),
566 datatype: ColumnDataType::String as _,
567 semantic_type: SemanticType::Tag as _,
568 ..Default::default()
569 },
570 ColumnSchema {
571 column_name: METADATA_SCHEMA_VALUE_COLUMN_NAME.to_string(),
572 datatype: ColumnDataType::String as _,
573 semantic_type: SemanticType::Field as _,
574 ..Default::default()
575 },
576 ];
577 let rows = Rows {
578 schema: cols,
579 rows: kv
580 .into_iter()
581 .map(|(key, value)| {
582 row(vec![
583 ValueData::TimestampMillisecondValue(0),
584 ValueData::StringValue(key),
585 ValueData::StringValue(value),
586 ])
587 })
588 .collect(),
589 };
590
591 RegionPutRequest {
592 skip_wal: false,
594 rows,
595 hint: None,
596 partition_expr_version: None,
597 }
598 }
599
600 fn build_delete_request(keys: &[String]) -> RegionDeleteRequest {
601 let cols = vec![
602 ColumnSchema {
603 column_name: METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME.to_string(),
604 datatype: ColumnDataType::TimestampMillisecond as _,
605 semantic_type: SemanticType::Timestamp as _,
606 ..Default::default()
607 },
608 ColumnSchema {
609 column_name: METADATA_SCHEMA_KEY_COLUMN_NAME.to_string(),
610 datatype: ColumnDataType::String as _,
611 semantic_type: SemanticType::Tag as _,
612 ..Default::default()
613 },
614 ];
615 let rows = keys
616 .iter()
617 .map(|key| {
618 row(vec![
619 ValueData::TimestampMillisecondValue(0),
620 ValueData::StringValue(key.clone()),
621 ])
622 })
623 .collect();
624 let rows = Rows { schema: cols, rows };
625
626 RegionDeleteRequest {
627 rows,
628 hint: None,
629 partition_expr_version: None,
630 }
631 }
632
633 pub async fn add_logical_regions(
635 &self,
636 physical_region_id: RegionId,
637 write_region_id: bool,
638 logical_regions: impl Iterator<Item = (RegionId, HashMap<&str, &ColumnMetadata>)>,
639 ) -> Result<()> {
640 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
641 let iter = logical_regions
642 .into_iter()
643 .flat_map(|(logical_region_id, column_metadatas)| {
644 if write_region_id {
645 Some((
646 MetadataRegion::concat_region_key(logical_region_id),
647 String::new(),
648 ))
649 } else {
650 None
651 }
652 .into_iter()
653 .chain(column_metadatas.into_iter().map(
654 move |(name, column_metadata)| {
655 (
656 MetadataRegion::concat_column_key(logical_region_id, name),
657 MetadataRegion::serialize_column_metadata(column_metadata),
658 )
659 },
660 ))
661 })
662 .collect::<Vec<_>>();
663
664 let put_request = MetadataRegion::build_put_request_from_iter(iter.into_iter());
665 self.write_metadata(metadata_region_id, RegionRequest::Put(put_request))
666 .await
667 }
668
669 pub async fn transform_logical_region_metadata(
678 &self,
679 physical_region_id: RegionId,
680 source_region_id: RegionId,
681 ) -> Result<()> {
682 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
683 let data_region_id = utils::to_data_region_id(physical_region_id);
684 let logical_regions = self
685 .logical_regions(data_region_id)
686 .await?
687 .into_iter()
688 .filter(|r| r.region_number() == source_region_id.region_number())
689 .collect::<Vec<_>>();
690 if logical_regions.is_empty() {
691 info!(
692 "No logical regions found from source region {}, physical region id: {}",
693 source_region_id, physical_region_id,
694 );
695 return Ok(());
696 }
697
698 let metadata = self.load_all(metadata_region_id).await?;
699 let mut output = Vec::new();
700 for logical_region_id in &logical_regions {
701 let prefix = MetadataRegion::concat_column_key_prefix(*logical_region_id);
702 get_all_with_prefix(&metadata, &prefix, |k, v| {
703 let (src_logical_region_id, column_name) = Self::parse_column_key(k)?.unwrap();
705 let new_key = MetadataRegion::concat_column_key(
707 RegionId::new(
708 src_logical_region_id.table_id(),
709 data_region_id.region_number(),
710 ),
711 &column_name,
712 );
713 output.push((new_key, v.to_string()));
714 Ok(())
715 })?;
716
717 let new_key = MetadataRegion::concat_region_key(RegionId::new(
718 logical_region_id.table_id(),
719 data_region_id.region_number(),
720 ));
721 output.push((new_key, String::new()));
722 }
723
724 if output.is_empty() {
725 warn!(
726 "No logical regions metadata found from source region {}, physical region id: {}",
727 source_region_id, physical_region_id
728 );
729 return Ok(());
730 }
731
732 debug!(
733 "Transform logical regions metadata to physical region {}, source region: {}, transformed metadata: {}",
734 data_region_id,
735 source_region_id,
736 output.len(),
737 );
738
739 let put_request = MetadataRegion::build_put_request_from_iter(output.into_iter());
740 self.write_metadata(metadata_region_id, RegionRequest::Put(put_request))
741 .await?;
742 info!(
743 "Transformed {} logical regions metadata to physical region {}, source region: {}",
744 logical_regions.len(),
745 data_region_id,
746 source_region_id
747 );
748 Ok(())
749 }
750}
751
752fn get_all_with_prefix(
753 region_metadata: &RegionMetadataCacheEntry,
754 prefix: &str,
755 mut callback: impl FnMut(&str, &str) -> Result<()>,
756) -> Result<()> {
757 let range = region_metadata.key_values.range(prefix.to_string()..);
758 for (k, v) in range {
759 if !k.starts_with(prefix) {
760 break;
761 }
762 callback(k, v)?;
763 }
764 Ok(())
765}
766
767#[cfg(test)]
768impl MetadataRegion {
769 pub async fn get(&self, region_id: RegionId, key: &str) -> Result<Option<String>> {
772 use datatypes::arrow::array::{Array, AsArray};
773
774 let filter_expr = datafusion::prelude::col(METADATA_SCHEMA_KEY_COLUMN_NAME)
775 .eq(datafusion::prelude::lit(key));
776
777 let projection = Some(vec![METADATA_SCHEMA_VALUE_COLUMN_INDEX]);
778 let scan_req = ScanRequest {
779 projection,
780 filters: vec![filter_expr],
781 ..Default::default()
782 };
783 let record_batch_stream = self
784 .mito
785 .scan_to_stream(region_id, scan_req)
786 .await
787 .context(MitoReadOperationSnafu)?;
788 let scan_result = common_recordbatch::util::collect(record_batch_stream)
789 .await
790 .context(CollectRecordBatchStreamSnafu)?;
791
792 let Some(first_batch) = scan_result.first() else {
793 return Ok(None);
794 };
795
796 let column = first_batch.column(0);
797 let column = column.as_string::<i32>();
798 let val = column.is_valid(0).then(|| column.value(0).to_string());
799
800 Ok(val)
801 }
802
803 pub async fn column_semantic_type(
805 &self,
806 physical_region_id: RegionId,
807 logical_region_id: RegionId,
808 column_name: &str,
809 ) -> Result<Option<SemanticType>> {
810 let region_id = utils::to_metadata_region_id(physical_region_id);
811 let column_key = Self::concat_column_key(logical_region_id, column_name);
812 let semantic_type = self.get(region_id, &column_key).await?;
813 semantic_type
814 .map(|s| Self::deserialize_column_metadata(&s).map(|c| c.semantic_type))
815 .transpose()
816 }
817}
818
819#[cfg(test)]
820mod test {
821 use datatypes::data_type::ConcreteDataType;
822 use datatypes::schema::ColumnSchema;
823
824 use super::*;
825 use crate::test_util::TestEnv;
826 use crate::utils::to_metadata_region_id;
827
828 #[test]
829 fn test_metadata_put_always_writes_wal() {
830 for entries in [
833 vec![],
834 vec![("region", "")],
835 vec![("region", ""), ("column", "metadata")],
836 ] {
837 let request = MetadataRegion::build_put_request_from_iter(
838 entries
839 .iter()
840 .map(|(key, value)| (key.to_string(), value.to_string())),
841 );
842 assert!(!request.skip_wal);
843 assert!(request.hint.is_none());
844 assert!(request.partition_expr_version.is_none());
845 let expected_rows = entries
846 .into_iter()
847 .map(|(key, value)| {
848 row(vec![
849 ValueData::TimestampMillisecondValue(0),
850 ValueData::StringValue(key.to_string()),
851 ValueData::StringValue(value.to_string()),
852 ])
853 })
854 .collect::<Vec<_>>();
855 assert_eq!(request.rows.rows, expected_rows);
856 }
857 }
858
859 #[test]
860 fn test_concat_table_key() {
861 let region_id = RegionId::new(1234, 7844);
862 let expected = "__region_5299989651108".to_string();
863 assert_eq!(MetadataRegion::concat_region_key(region_id), expected);
864 }
865
866 #[test]
867 fn test_concat_column_key() {
868 let region_id = RegionId::new(8489, 9184);
869 let column_name = "my_column";
870 let expected = "__column_36459977384928_bXlfY29sdW1u".to_string();
871 assert_eq!(
872 MetadataRegion::concat_column_key(region_id, column_name),
873 expected
874 );
875 }
876
877 #[test]
878 fn test_parse_table_key() {
879 let region_id = RegionId::new(87474, 10607);
880 let encoded = MetadataRegion::concat_column_key(region_id, "my_column");
881 assert_eq!(encoded, "__column_375697969260911_bXlfY29sdW1u");
882
883 let decoded = MetadataRegion::parse_column_key(&encoded).unwrap();
884 assert_eq!(decoded, Some((region_id, "my_column".to_string())));
885 }
886
887 #[test]
888 fn test_parse_valid_column_key() {
889 let region_id = RegionId::new(176, 910);
890 let encoded = MetadataRegion::concat_column_key(region_id, "my_column");
891 assert_eq!(encoded, "__column_755914245006_bXlfY29sdW1u");
892
893 let decoded = MetadataRegion::parse_column_key(&encoded).unwrap();
894 assert_eq!(decoded, Some((region_id, "my_column".to_string())));
895 }
896
897 #[test]
898 fn test_parse_invalid_column_key() {
899 let key = "__column_asdfasd_????";
900 let result = MetadataRegion::parse_column_key(key);
901 assert!(result.is_err());
902 }
903
904 #[test]
905 fn test_serialize_column_metadata() {
906 let semantic_type = SemanticType::Tag;
907 let column_metadata = ColumnMetadata {
908 column_schema: ColumnSchema::new("blabla", ConcreteDataType::string_datatype(), false),
909 semantic_type,
910 column_id: 5,
911 };
912 let old_fmt = "{\"column_schema\":{\"name\":\"blabla\",\"data_type\":{\"String\":null},\"is_nullable\":false,\"is_time_index\":false,\"default_constraint\":null,\"metadata\":{}},\"semantic_type\":\"Tag\",\"column_id\":5}".to_string();
913 let new_fmt = "{\"column_schema\":{\"name\":\"blabla\",\"data_type\":{\"String\":{\"size_type\":\"Utf8\"}},\"is_nullable\":false,\"is_time_index\":false,\"default_constraint\":null,\"metadata\":{}},\"semantic_type\":\"Tag\",\"column_id\":5}".to_string();
914 assert_eq!(
915 MetadataRegion::serialize_column_metadata(&column_metadata),
916 new_fmt
917 );
918 assert_eq!(
920 MetadataRegion::deserialize_column_metadata(&old_fmt).unwrap(),
921 column_metadata
922 );
923 assert_eq!(
924 MetadataRegion::deserialize_column_metadata(&new_fmt).unwrap(),
925 column_metadata
926 );
927
928 let semantic_type = "\"Invalid Column Metadata\"";
929 assert!(MetadataRegion::deserialize_column_metadata(semantic_type).is_err());
930 }
931
932 fn test_column_metadatas() -> HashMap<String, ColumnMetadata> {
933 HashMap::from([
934 (
935 "label1".to_string(),
936 ColumnMetadata {
937 column_schema: ColumnSchema::new(
938 "label1".to_string(),
939 ConcreteDataType::string_datatype(),
940 false,
941 ),
942 semantic_type: SemanticType::Tag,
943 column_id: 5,
944 },
945 ),
946 (
947 "label2".to_string(),
948 ColumnMetadata {
949 column_schema: ColumnSchema::new(
950 "label2".to_string(),
951 ConcreteDataType::string_datatype(),
952 false,
953 ),
954 semantic_type: SemanticType::Tag,
955 column_id: 5,
956 },
957 ),
958 ])
959 }
960
961 async fn add_test_logical_region(
962 metadata_region: &MetadataRegion,
963 physical_region_id: RegionId,
964 logical_region_id: RegionId,
965 ) -> Result<()> {
966 let column_metadatas = test_column_metadatas();
967 let logical_regions = std::iter::once((
968 logical_region_id,
969 column_metadatas
970 .iter()
971 .map(|(name, metadata)| (name.as_str(), metadata))
972 .collect::<HashMap<_, _>>(),
973 ));
974 metadata_region
975 .add_logical_regions(physical_region_id, true, logical_regions)
976 .await
977 }
978
979 #[tokio::test]
980 async fn add_logical_regions_to_meta_region() {
981 let env = TestEnv::new().await;
982 env.init_metric_region().await;
983 let metadata_region = env.metadata_region();
984 let physical_region_id = to_metadata_region_id(env.default_physical_region_id());
985 let column_metadatas = test_column_metadatas();
986 let logical_region_id = RegionId::new(1024, 1);
987
988 let iter = vec![(
989 logical_region_id,
990 column_metadatas
991 .iter()
992 .map(|(k, v)| (k.as_str(), v))
993 .collect::<HashMap<_, _>>(),
994 )];
995 metadata_region
996 .add_logical_regions(physical_region_id, true, iter.into_iter())
997 .await
998 .unwrap();
999 let iter = vec![(
1001 logical_region_id,
1002 column_metadatas
1003 .iter()
1004 .map(|(k, v)| (k.as_str(), v))
1005 .collect::<HashMap<_, _>>(),
1006 )];
1007 metadata_region
1008 .add_logical_regions(physical_region_id, true, iter.into_iter())
1009 .await
1010 .unwrap();
1011
1012 let logical_regions = metadata_region
1014 .logical_regions(physical_region_id)
1015 .await
1016 .unwrap();
1017 assert_eq!(logical_regions.len(), 2);
1018
1019 let logical_columns = metadata_region
1021 .logical_columns(physical_region_id, logical_region_id)
1022 .await
1023 .unwrap()
1024 .into_iter()
1025 .collect::<HashMap<_, _>>();
1026 assert_eq!(logical_columns.len(), 2);
1027 assert_eq!(column_metadatas, logical_columns);
1028 }
1029
1030 #[tokio::test]
1031 async fn metadata_writes_are_synchronized_per_region() {
1032 let env = TestEnv::new().await;
1033 env.init_metric_region().await;
1034 let other_physical_region_id = RegionId::new(2, 2);
1035 env.create_physical_region(other_physical_region_id, "/test_dir2", vec![])
1036 .await;
1037 let metadata_region = Arc::new(env.metadata_region());
1038 let physical_region_id = env.default_physical_region_id();
1039 let metadata_region_id = to_metadata_region_id(physical_region_id);
1040
1041 let (snapshot_loaded_tx, snapshot_loaded_rx) = tokio::sync::oneshot::channel();
1042 let (release_snapshot_tx, release_snapshot_rx) = tokio::sync::oneshot::channel();
1043 let cache_fill = {
1044 let metadata_region = Arc::clone(&metadata_region);
1045 tokio::spawn(async move {
1046 let cache_access_lock = metadata_region.cache_access_lock(metadata_region_id);
1047 let _cache_guard = cache_access_lock.read().await;
1048 metadata_region
1049 .cache
1050 .try_get_with(metadata_region_id, async {
1051 let snapshot = metadata_region.load_all(metadata_region_id).await?;
1052 snapshot_loaded_tx.send(()).unwrap();
1053 release_snapshot_rx.await.unwrap();
1054 Ok::<_, crate::error::Error>(snapshot)
1055 })
1056 .await
1057 .unwrap();
1058 })
1059 };
1060 snapshot_loaded_rx.await.unwrap();
1061
1062 let logical_region_id = RegionId::new(1024, 1);
1063 let metadata_write = {
1064 let metadata_region = Arc::clone(&metadata_region);
1065 tokio::spawn(async move {
1066 add_test_logical_region(&metadata_region, physical_region_id, logical_region_id)
1067 .await
1068 })
1069 };
1070 let mut metadata_write = metadata_write;
1071 assert!(
1072 tokio::time::timeout(std::time::Duration::from_millis(50), &mut metadata_write)
1073 .await
1074 .is_err()
1075 );
1076
1077 let other_logical_region_id = RegionId::new(2048, 2);
1078 tokio::time::timeout(
1079 std::time::Duration::from_secs(5),
1080 add_test_logical_region(
1081 &metadata_region,
1082 other_physical_region_id,
1083 other_logical_region_id,
1084 ),
1085 )
1086 .await
1087 .unwrap()
1088 .unwrap();
1089
1090 release_snapshot_tx.send(()).unwrap();
1091 cache_fill.await.unwrap();
1092 metadata_write.await.unwrap().unwrap();
1093
1094 let logical_columns = metadata_region
1095 .logical_columns(physical_region_id, logical_region_id)
1096 .await
1097 .unwrap();
1098 assert_eq!(logical_columns.len(), 2);
1099 assert!(
1100 metadata_region
1101 .cache_access_locks
1102 .lock()
1103 .unwrap()
1104 .is_empty()
1105 );
1106 }
1107
1108 #[tokio::test]
1109 async fn cache_access_locks_self_clean() {
1110 let env = TestEnv::new().await;
1111 env.init_metric_region().await;
1112 let metadata_region = env.metadata_region();
1113 let physical_region_id = env.default_physical_region_id();
1114 let metadata_region_id = to_metadata_region_id(physical_region_id);
1115 let registry_len = || metadata_region.cache_access_locks.lock().unwrap().len();
1116
1117 let logical_region_id = RegionId::new(1024, 1);
1119 add_test_logical_region(&metadata_region, physical_region_id, logical_region_id)
1120 .await
1121 .unwrap();
1122 metadata_region
1123 .logical_columns(physical_region_id, logical_region_id)
1124 .await
1125 .unwrap();
1126 assert_eq!(registry_len(), 0);
1127
1128 let first = metadata_region.cache_access_lock(metadata_region_id);
1130 let second = metadata_region.cache_access_lock(metadata_region_id);
1131 assert!(Arc::ptr_eq(
1132 first.lock.as_ref().unwrap(),
1133 second.lock.as_ref().unwrap()
1134 ));
1135 drop(first);
1136 assert_eq!(registry_len(), 1);
1137 drop(second);
1138 assert_eq!(registry_len(), 0);
1139
1140 let third = metadata_region.cache_access_lock(metadata_region_id);
1142 assert_eq!(registry_len(), 1);
1143 drop(third);
1144 assert_eq!(registry_len(), 0);
1145 }
1146}