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 self.mito
542 .handle_request(metadata_region_id, request)
543 .await
544 .context(MitoWriteOperationSnafu)?;
545 self.cache.invalidate(&metadata_region_id).await;
546
547 Ok(())
548 }
549
550 pub(crate) fn build_put_request_from_iter(
551 kv: impl Iterator<Item = (String, String)>,
552 ) -> RegionPutRequest {
553 let cols = vec![
554 ColumnSchema {
555 column_name: METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME.to_string(),
556 datatype: ColumnDataType::TimestampMillisecond as _,
557 semantic_type: SemanticType::Timestamp as _,
558 ..Default::default()
559 },
560 ColumnSchema {
561 column_name: METADATA_SCHEMA_KEY_COLUMN_NAME.to_string(),
562 datatype: ColumnDataType::String as _,
563 semantic_type: SemanticType::Tag as _,
564 ..Default::default()
565 },
566 ColumnSchema {
567 column_name: METADATA_SCHEMA_VALUE_COLUMN_NAME.to_string(),
568 datatype: ColumnDataType::String as _,
569 semantic_type: SemanticType::Field as _,
570 ..Default::default()
571 },
572 ];
573 let rows = Rows {
574 schema: cols,
575 rows: kv
576 .into_iter()
577 .map(|(key, value)| {
578 row(vec![
579 ValueData::TimestampMillisecondValue(0),
580 ValueData::StringValue(key),
581 ValueData::StringValue(value),
582 ])
583 })
584 .collect(),
585 };
586
587 RegionPutRequest {
588 rows,
589 hint: None,
590 partition_expr_version: None,
591 }
592 }
593
594 fn build_delete_request(keys: &[String]) -> RegionDeleteRequest {
595 let cols = vec![
596 ColumnSchema {
597 column_name: METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME.to_string(),
598 datatype: ColumnDataType::TimestampMillisecond as _,
599 semantic_type: SemanticType::Timestamp as _,
600 ..Default::default()
601 },
602 ColumnSchema {
603 column_name: METADATA_SCHEMA_KEY_COLUMN_NAME.to_string(),
604 datatype: ColumnDataType::String as _,
605 semantic_type: SemanticType::Tag as _,
606 ..Default::default()
607 },
608 ];
609 let rows = keys
610 .iter()
611 .map(|key| {
612 row(vec![
613 ValueData::TimestampMillisecondValue(0),
614 ValueData::StringValue(key.clone()),
615 ])
616 })
617 .collect();
618 let rows = Rows { schema: cols, rows };
619
620 RegionDeleteRequest {
621 rows,
622 hint: None,
623 partition_expr_version: None,
624 }
625 }
626
627 pub async fn add_logical_regions(
629 &self,
630 physical_region_id: RegionId,
631 write_region_id: bool,
632 logical_regions: impl Iterator<Item = (RegionId, HashMap<&str, &ColumnMetadata>)>,
633 ) -> Result<()> {
634 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
635 let iter = logical_regions
636 .into_iter()
637 .flat_map(|(logical_region_id, column_metadatas)| {
638 if write_region_id {
639 Some((
640 MetadataRegion::concat_region_key(logical_region_id),
641 String::new(),
642 ))
643 } else {
644 None
645 }
646 .into_iter()
647 .chain(column_metadatas.into_iter().map(
648 move |(name, column_metadata)| {
649 (
650 MetadataRegion::concat_column_key(logical_region_id, name),
651 MetadataRegion::serialize_column_metadata(column_metadata),
652 )
653 },
654 ))
655 })
656 .collect::<Vec<_>>();
657
658 let put_request = MetadataRegion::build_put_request_from_iter(iter.into_iter());
659 self.write_metadata(metadata_region_id, RegionRequest::Put(put_request))
660 .await
661 }
662
663 pub async fn transform_logical_region_metadata(
672 &self,
673 physical_region_id: RegionId,
674 source_region_id: RegionId,
675 ) -> Result<()> {
676 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
677 let data_region_id = utils::to_data_region_id(physical_region_id);
678 let logical_regions = self
679 .logical_regions(data_region_id)
680 .await?
681 .into_iter()
682 .filter(|r| r.region_number() == source_region_id.region_number())
683 .collect::<Vec<_>>();
684 if logical_regions.is_empty() {
685 info!(
686 "No logical regions found from source region {}, physical region id: {}",
687 source_region_id, physical_region_id,
688 );
689 return Ok(());
690 }
691
692 let metadata = self.load_all(metadata_region_id).await?;
693 let mut output = Vec::new();
694 for logical_region_id in &logical_regions {
695 let prefix = MetadataRegion::concat_column_key_prefix(*logical_region_id);
696 get_all_with_prefix(&metadata, &prefix, |k, v| {
697 let (src_logical_region_id, column_name) = Self::parse_column_key(k)?.unwrap();
699 let new_key = MetadataRegion::concat_column_key(
701 RegionId::new(
702 src_logical_region_id.table_id(),
703 data_region_id.region_number(),
704 ),
705 &column_name,
706 );
707 output.push((new_key, v.to_string()));
708 Ok(())
709 })?;
710
711 let new_key = MetadataRegion::concat_region_key(RegionId::new(
712 logical_region_id.table_id(),
713 data_region_id.region_number(),
714 ));
715 output.push((new_key, String::new()));
716 }
717
718 if output.is_empty() {
719 warn!(
720 "No logical regions metadata found from source region {}, physical region id: {}",
721 source_region_id, physical_region_id
722 );
723 return Ok(());
724 }
725
726 debug!(
727 "Transform logical regions metadata to physical region {}, source region: {}, transformed metadata: {}",
728 data_region_id,
729 source_region_id,
730 output.len(),
731 );
732
733 let put_request = MetadataRegion::build_put_request_from_iter(output.into_iter());
734 self.write_metadata(metadata_region_id, RegionRequest::Put(put_request))
735 .await?;
736 info!(
737 "Transformed {} logical regions metadata to physical region {}, source region: {}",
738 logical_regions.len(),
739 data_region_id,
740 source_region_id
741 );
742 Ok(())
743 }
744}
745
746fn get_all_with_prefix(
747 region_metadata: &RegionMetadataCacheEntry,
748 prefix: &str,
749 mut callback: impl FnMut(&str, &str) -> Result<()>,
750) -> Result<()> {
751 let range = region_metadata.key_values.range(prefix.to_string()..);
752 for (k, v) in range {
753 if !k.starts_with(prefix) {
754 break;
755 }
756 callback(k, v)?;
757 }
758 Ok(())
759}
760
761#[cfg(test)]
762impl MetadataRegion {
763 pub async fn get(&self, region_id: RegionId, key: &str) -> Result<Option<String>> {
766 use datatypes::arrow::array::{Array, AsArray};
767
768 let filter_expr = datafusion::prelude::col(METADATA_SCHEMA_KEY_COLUMN_NAME)
769 .eq(datafusion::prelude::lit(key));
770
771 let projection = Some(vec![METADATA_SCHEMA_VALUE_COLUMN_INDEX]);
772 let scan_req = ScanRequest {
773 projection,
774 filters: vec![filter_expr],
775 ..Default::default()
776 };
777 let record_batch_stream = self
778 .mito
779 .scan_to_stream(region_id, scan_req)
780 .await
781 .context(MitoReadOperationSnafu)?;
782 let scan_result = common_recordbatch::util::collect(record_batch_stream)
783 .await
784 .context(CollectRecordBatchStreamSnafu)?;
785
786 let Some(first_batch) = scan_result.first() else {
787 return Ok(None);
788 };
789
790 let column = first_batch.column(0);
791 let column = column.as_string::<i32>();
792 let val = column.is_valid(0).then(|| column.value(0).to_string());
793
794 Ok(val)
795 }
796
797 pub async fn column_semantic_type(
799 &self,
800 physical_region_id: RegionId,
801 logical_region_id: RegionId,
802 column_name: &str,
803 ) -> Result<Option<SemanticType>> {
804 let region_id = utils::to_metadata_region_id(physical_region_id);
805 let column_key = Self::concat_column_key(logical_region_id, column_name);
806 let semantic_type = self.get(region_id, &column_key).await?;
807 semantic_type
808 .map(|s| Self::deserialize_column_metadata(&s).map(|c| c.semantic_type))
809 .transpose()
810 }
811}
812
813#[cfg(test)]
814mod test {
815 use datatypes::data_type::ConcreteDataType;
816 use datatypes::schema::ColumnSchema;
817
818 use super::*;
819 use crate::test_util::TestEnv;
820 use crate::utils::to_metadata_region_id;
821
822 #[test]
823 fn test_concat_table_key() {
824 let region_id = RegionId::new(1234, 7844);
825 let expected = "__region_5299989651108".to_string();
826 assert_eq!(MetadataRegion::concat_region_key(region_id), expected);
827 }
828
829 #[test]
830 fn test_concat_column_key() {
831 let region_id = RegionId::new(8489, 9184);
832 let column_name = "my_column";
833 let expected = "__column_36459977384928_bXlfY29sdW1u".to_string();
834 assert_eq!(
835 MetadataRegion::concat_column_key(region_id, column_name),
836 expected
837 );
838 }
839
840 #[test]
841 fn test_parse_table_key() {
842 let region_id = RegionId::new(87474, 10607);
843 let encoded = MetadataRegion::concat_column_key(region_id, "my_column");
844 assert_eq!(encoded, "__column_375697969260911_bXlfY29sdW1u");
845
846 let decoded = MetadataRegion::parse_column_key(&encoded).unwrap();
847 assert_eq!(decoded, Some((region_id, "my_column".to_string())));
848 }
849
850 #[test]
851 fn test_parse_valid_column_key() {
852 let region_id = RegionId::new(176, 910);
853 let encoded = MetadataRegion::concat_column_key(region_id, "my_column");
854 assert_eq!(encoded, "__column_755914245006_bXlfY29sdW1u");
855
856 let decoded = MetadataRegion::parse_column_key(&encoded).unwrap();
857 assert_eq!(decoded, Some((region_id, "my_column".to_string())));
858 }
859
860 #[test]
861 fn test_parse_invalid_column_key() {
862 let key = "__column_asdfasd_????";
863 let result = MetadataRegion::parse_column_key(key);
864 assert!(result.is_err());
865 }
866
867 #[test]
868 fn test_serialize_column_metadata() {
869 let semantic_type = SemanticType::Tag;
870 let column_metadata = ColumnMetadata {
871 column_schema: ColumnSchema::new("blabla", ConcreteDataType::string_datatype(), false),
872 semantic_type,
873 column_id: 5,
874 };
875 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();
876 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();
877 assert_eq!(
878 MetadataRegion::serialize_column_metadata(&column_metadata),
879 new_fmt
880 );
881 assert_eq!(
883 MetadataRegion::deserialize_column_metadata(&old_fmt).unwrap(),
884 column_metadata
885 );
886 assert_eq!(
887 MetadataRegion::deserialize_column_metadata(&new_fmt).unwrap(),
888 column_metadata
889 );
890
891 let semantic_type = "\"Invalid Column Metadata\"";
892 assert!(MetadataRegion::deserialize_column_metadata(semantic_type).is_err());
893 }
894
895 fn test_column_metadatas() -> HashMap<String, ColumnMetadata> {
896 HashMap::from([
897 (
898 "label1".to_string(),
899 ColumnMetadata {
900 column_schema: ColumnSchema::new(
901 "label1".to_string(),
902 ConcreteDataType::string_datatype(),
903 false,
904 ),
905 semantic_type: SemanticType::Tag,
906 column_id: 5,
907 },
908 ),
909 (
910 "label2".to_string(),
911 ColumnMetadata {
912 column_schema: ColumnSchema::new(
913 "label2".to_string(),
914 ConcreteDataType::string_datatype(),
915 false,
916 ),
917 semantic_type: SemanticType::Tag,
918 column_id: 5,
919 },
920 ),
921 ])
922 }
923
924 async fn add_test_logical_region(
925 metadata_region: &MetadataRegion,
926 physical_region_id: RegionId,
927 logical_region_id: RegionId,
928 ) -> Result<()> {
929 let column_metadatas = test_column_metadatas();
930 let logical_regions = std::iter::once((
931 logical_region_id,
932 column_metadatas
933 .iter()
934 .map(|(name, metadata)| (name.as_str(), metadata))
935 .collect::<HashMap<_, _>>(),
936 ));
937 metadata_region
938 .add_logical_regions(physical_region_id, true, logical_regions)
939 .await
940 }
941
942 #[tokio::test]
943 async fn add_logical_regions_to_meta_region() {
944 let env = TestEnv::new().await;
945 env.init_metric_region().await;
946 let metadata_region = env.metadata_region();
947 let physical_region_id = to_metadata_region_id(env.default_physical_region_id());
948 let column_metadatas = test_column_metadatas();
949 let logical_region_id = RegionId::new(1024, 1);
950
951 let iter = vec![(
952 logical_region_id,
953 column_metadatas
954 .iter()
955 .map(|(k, v)| (k.as_str(), v))
956 .collect::<HashMap<_, _>>(),
957 )];
958 metadata_region
959 .add_logical_regions(physical_region_id, true, iter.into_iter())
960 .await
961 .unwrap();
962 let iter = vec![(
964 logical_region_id,
965 column_metadatas
966 .iter()
967 .map(|(k, v)| (k.as_str(), v))
968 .collect::<HashMap<_, _>>(),
969 )];
970 metadata_region
971 .add_logical_regions(physical_region_id, true, iter.into_iter())
972 .await
973 .unwrap();
974
975 let logical_regions = metadata_region
977 .logical_regions(physical_region_id)
978 .await
979 .unwrap();
980 assert_eq!(logical_regions.len(), 2);
981
982 let logical_columns = metadata_region
984 .logical_columns(physical_region_id, logical_region_id)
985 .await
986 .unwrap()
987 .into_iter()
988 .collect::<HashMap<_, _>>();
989 assert_eq!(logical_columns.len(), 2);
990 assert_eq!(column_metadatas, logical_columns);
991 }
992
993 #[tokio::test]
994 async fn metadata_writes_are_synchronized_per_region() {
995 let env = TestEnv::new().await;
996 env.init_metric_region().await;
997 let other_physical_region_id = RegionId::new(2, 2);
998 env.create_physical_region(other_physical_region_id, "/test_dir2", vec![])
999 .await;
1000 let metadata_region = Arc::new(env.metadata_region());
1001 let physical_region_id = env.default_physical_region_id();
1002 let metadata_region_id = to_metadata_region_id(physical_region_id);
1003
1004 let (snapshot_loaded_tx, snapshot_loaded_rx) = tokio::sync::oneshot::channel();
1005 let (release_snapshot_tx, release_snapshot_rx) = tokio::sync::oneshot::channel();
1006 let cache_fill = {
1007 let metadata_region = Arc::clone(&metadata_region);
1008 tokio::spawn(async move {
1009 let cache_access_lock = metadata_region.cache_access_lock(metadata_region_id);
1010 let _cache_guard = cache_access_lock.read().await;
1011 metadata_region
1012 .cache
1013 .try_get_with(metadata_region_id, async {
1014 let snapshot = metadata_region.load_all(metadata_region_id).await?;
1015 snapshot_loaded_tx.send(()).unwrap();
1016 release_snapshot_rx.await.unwrap();
1017 Ok::<_, crate::error::Error>(snapshot)
1018 })
1019 .await
1020 .unwrap();
1021 })
1022 };
1023 snapshot_loaded_rx.await.unwrap();
1024
1025 let logical_region_id = RegionId::new(1024, 1);
1026 let metadata_write = {
1027 let metadata_region = Arc::clone(&metadata_region);
1028 tokio::spawn(async move {
1029 add_test_logical_region(&metadata_region, physical_region_id, logical_region_id)
1030 .await
1031 })
1032 };
1033 let mut metadata_write = metadata_write;
1034 assert!(
1035 tokio::time::timeout(std::time::Duration::from_millis(50), &mut metadata_write)
1036 .await
1037 .is_err()
1038 );
1039
1040 let other_logical_region_id = RegionId::new(2048, 2);
1041 tokio::time::timeout(
1042 std::time::Duration::from_secs(5),
1043 add_test_logical_region(
1044 &metadata_region,
1045 other_physical_region_id,
1046 other_logical_region_id,
1047 ),
1048 )
1049 .await
1050 .unwrap()
1051 .unwrap();
1052
1053 release_snapshot_tx.send(()).unwrap();
1054 cache_fill.await.unwrap();
1055 metadata_write.await.unwrap().unwrap();
1056
1057 let logical_columns = metadata_region
1058 .logical_columns(physical_region_id, logical_region_id)
1059 .await
1060 .unwrap();
1061 assert_eq!(logical_columns.len(), 2);
1062 assert!(
1063 metadata_region
1064 .cache_access_locks
1065 .lock()
1066 .unwrap()
1067 .is_empty()
1068 );
1069 }
1070
1071 #[tokio::test]
1072 async fn cache_access_locks_self_clean() {
1073 let env = TestEnv::new().await;
1074 env.init_metric_region().await;
1075 let metadata_region = env.metadata_region();
1076 let physical_region_id = env.default_physical_region_id();
1077 let metadata_region_id = to_metadata_region_id(physical_region_id);
1078 let registry_len = || metadata_region.cache_access_locks.lock().unwrap().len();
1079
1080 let logical_region_id = RegionId::new(1024, 1);
1082 add_test_logical_region(&metadata_region, physical_region_id, logical_region_id)
1083 .await
1084 .unwrap();
1085 metadata_region
1086 .logical_columns(physical_region_id, logical_region_id)
1087 .await
1088 .unwrap();
1089 assert_eq!(registry_len(), 0);
1090
1091 let first = metadata_region.cache_access_lock(metadata_region_id);
1093 let second = metadata_region.cache_access_lock(metadata_region_id);
1094 assert!(Arc::ptr_eq(
1095 first.lock.as_ref().unwrap(),
1096 second.lock.as_ref().unwrap()
1097 ));
1098 drop(first);
1099 assert_eq!(registry_len(), 1);
1100 drop(second);
1101 assert_eq!(registry_len(), 0);
1102
1103 let third = metadata_region.cache_access_lock(metadata_region_id);
1105 assert_eq!(registry_len(), 1);
1106 drop(third);
1107 assert_eq!(registry_len(), 0);
1108 }
1109}