1use std::collections::HashMap;
18use std::fmt;
19use std::fmt::{Debug, Formatter};
20use std::num::NonZeroU64;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex, RwLock};
23
24use base64::prelude::{BASE64_STANDARD, Engine};
25use bytes::Bytes;
26use common_base::readable_size::ReadableSize;
27use common_telemetry::{debug, error};
28use common_time::Timestamp;
29use partition::expr::PartitionExpr;
30use serde::{Deserialize, Serialize};
31use smallvec::SmallVec;
32use store_api::metadata::ColumnMetadata;
33use store_api::region_request::PathType;
34use store_api::storage::{ColumnId, FileId, IndexVersion, RegionId};
35
36use crate::access_layer::AccessLayerRef;
37use crate::cache::CacheManagerRef;
38use crate::cache::file_cache::{FileType, IndexKey};
39use crate::sst::file_purger::FilePurgerRef;
40use crate::sst::location;
41use crate::sst::parquet::SstInfo;
42
43fn serialize_bytes_option<S>(bytes: &Option<Bytes>, serializer: S) -> Result<S::Ok, S::Error>
45where
46 S: serde::Serializer,
47{
48 match bytes {
49 None => serializer.serialize_none(),
50 Some(b) => serializer.serialize_some(&BASE64_STANDARD.encode(b)),
51 }
52}
53
54fn deserialize_bytes_option<'de, D>(deserializer: D) -> Result<Option<Bytes>, D::Error>
55where
56 D: serde::Deserializer<'de>,
57{
58 let opt: Option<String> = Option::deserialize(deserializer)?;
59 match opt {
60 None => Ok(None),
61 Some(s) => {
62 let decoded = BASE64_STANDARD
63 .decode(&s)
64 .map_err(serde::de::Error::custom)?;
65 Ok(Some(Bytes::from(decoded)))
66 }
67 }
68}
69
70fn serialize_partition_expr<S>(
72 partition_expr: &Option<PartitionExpr>,
73 serializer: S,
74) -> Result<S::Ok, S::Error>
75where
76 S: serde::Serializer,
77{
78 use serde::ser::Error;
79
80 match partition_expr {
81 None => serializer.serialize_none(),
82 Some(expr) => {
83 let json_str = expr.as_json_str().map_err(S::Error::custom)?;
84 serializer.serialize_some(&json_str)
85 }
86 }
87}
88
89fn deserialize_partition_expr<'de, D>(deserializer: D) -> Result<Option<PartitionExpr>, D::Error>
90where
91 D: serde::Deserializer<'de>,
92{
93 use serde::de::Error;
94
95 let opt_json_str: Option<String> = Option::deserialize(deserializer)?;
96 match opt_json_str {
97 None => Ok(None),
98 Some(json_str) => {
99 if json_str.is_empty() {
100 Ok(None)
102 } else {
103 PartitionExpr::from_json_str(&json_str).map_err(D::Error::custom)
105 }
106 }
107 }
108}
109
110pub type Level = u8;
112pub const MAX_LEVEL: Level = 2;
114pub type IndexTypes = SmallVec<[IndexType; 4]>;
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121pub struct RegionFileId {
122 region_id: RegionId,
124 file_id: FileId,
126}
127
128impl RegionFileId {
129 pub fn new(region_id: RegionId, file_id: FileId) -> Self {
131 Self { region_id, file_id }
132 }
133
134 pub fn region_id(&self) -> RegionId {
136 self.region_id
137 }
138
139 pub fn file_id(&self) -> FileId {
141 self.file_id
142 }
143}
144
145impl fmt::Display for RegionFileId {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}/{}", self.region_id, self.file_id)
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
153pub struct RegionIndexId {
154 pub file_id: RegionFileId,
155 pub version: IndexVersion,
156}
157
158impl RegionIndexId {
159 pub fn new(file_id: RegionFileId, version: IndexVersion) -> Self {
160 Self { file_id, version }
161 }
162
163 pub fn region_id(&self) -> RegionId {
164 self.file_id.region_id
165 }
166
167 pub fn file_id(&self) -> FileId {
168 self.file_id.file_id
169 }
170}
171
172impl fmt::Display for RegionIndexId {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 if self.version == 0 {
175 write!(f, "{}/{}", self.file_id.region_id, self.file_id.file_id)
176 } else {
177 write!(
178 f,
179 "{}/{}.{}",
180 self.file_id.region_id, self.file_id.file_id, self.version
181 )
182 }
183 }
184}
185
186pub type FileTimeRange = (Timestamp, Timestamp);
189
190pub(crate) fn overlaps(l: &FileTimeRange, r: &FileTimeRange) -> bool {
192 let (l, r) = if l.0 <= r.0 { (l, r) } else { (r, l) };
193 let (_, l_end) = l;
194 let (r_start, _) = r;
195
196 r_start <= l_end
197}
198
199#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
201#[serde(default)]
202pub struct FileMeta {
203 pub region_id: RegionId,
205 pub file_id: FileId,
207 pub time_range: FileTimeRange,
210 pub level: Level,
212 pub file_size: u64,
214 pub max_row_group_uncompressed_size: u64,
216 pub available_indexes: IndexTypes,
218 pub indexes: Vec<ColumnIndexMetadata>,
228 pub index_file_size: u64,
230 pub index_version: u64,
234 pub num_rows: u64,
240 pub num_row_groups: u64,
246 pub sequence: Option<NonZeroU64>,
251 #[serde(
259 serialize_with = "serialize_partition_expr",
260 deserialize_with = "deserialize_partition_expr"
261 )]
262 pub partition_expr: Option<PartitionExpr>,
263 pub num_series: u64,
267 #[serde(
270 default,
271 skip_serializing_if = "Option::is_none",
272 serialize_with = "serialize_bytes_option",
273 deserialize_with = "deserialize_bytes_option"
274 )]
275 pub primary_key_min: Option<Bytes>,
276 #[serde(
279 default,
280 skip_serializing_if = "Option::is_none",
281 serialize_with = "serialize_bytes_option",
282 deserialize_with = "deserialize_bytes_option"
283 )]
284 pub primary_key_max: Option<Bytes>,
285}
286
287impl Debug for FileMeta {
288 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
289 let mut debug_struct = f.debug_struct("FileMeta");
290 debug_struct
291 .field("region_id", &self.region_id)
292 .field_with("file_id", |f| write!(f, "{} ", self.file_id))
293 .field_with("time_range", |f| {
294 write!(
295 f,
296 "({}, {}) ",
297 self.time_range.0.to_iso8601_string(),
298 self.time_range.1.to_iso8601_string()
299 )
300 })
301 .field("level", &self.level)
302 .field("file_size", &ReadableSize(self.file_size))
303 .field(
304 "max_row_group_uncompressed_size",
305 &ReadableSize(self.max_row_group_uncompressed_size),
306 );
307 if !self.available_indexes.is_empty() {
308 debug_struct
309 .field("available_indexes", &self.available_indexes)
310 .field("indexes", &self.indexes)
311 .field("index_file_size", &ReadableSize(self.index_file_size));
312 }
313 debug_struct
314 .field("num_rows", &self.num_rows)
315 .field("num_row_groups", &self.num_row_groups)
316 .field_with("sequence", |f| match self.sequence {
317 None => {
318 write!(f, "None")
319 }
320 Some(seq) => {
321 write!(f, "{}", seq)
322 }
323 })
324 .field("partition_expr", &self.partition_expr)
325 .field("num_series", &self.num_series);
326 if self.primary_key_min.is_some() || self.primary_key_max.is_some() {
327 debug_struct
328 .field(
329 "primary_key_min",
330 &self.primary_key_min.as_ref().map(|b| b.len()),
331 )
332 .field(
333 "primary_key_max",
334 &self.primary_key_max.as_ref().map(|b| b.len()),
335 );
336 }
337 debug_struct.finish()
338 }
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
343pub enum IndexType {
344 InvertedIndex,
346 FulltextIndex,
348 BloomFilterIndex,
350 #[cfg(feature = "vector_index")]
352 VectorIndex,
353}
354
355#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
365#[serde(default)]
366pub struct ColumnIndexMetadata {
367 pub column_id: ColumnId,
369 pub created_indexes: IndexTypes,
371}
372
373impl FileMeta {
374 pub fn primary_key_range(&self) -> Option<(Bytes, Bytes)> {
376 match (&self.primary_key_min, &self.primary_key_max) {
377 (Some(min), Some(max)) => Some((min.clone(), max.clone())),
378 _ => None,
379 }
380 }
381
382 pub fn exists_index(&self) -> bool {
383 !self.available_indexes.is_empty()
384 }
385
386 pub fn index_version(&self) -> Option<IndexVersion> {
387 if self.exists_index() {
388 Some(self.index_version)
389 } else {
390 None
391 }
392 }
393
394 pub fn is_index_up_to_date(&self, other: &FileMeta) -> bool {
396 self.exists_index() && other.exists_index() && self.index_version >= other.index_version
397 }
398
399 pub fn inverted_index_available(&self) -> bool {
401 self.available_indexes.contains(&IndexType::InvertedIndex)
402 }
403
404 pub fn fulltext_index_available(&self) -> bool {
406 self.available_indexes.contains(&IndexType::FulltextIndex)
407 }
408
409 pub fn bloom_filter_index_available(&self) -> bool {
411 self.available_indexes
412 .contains(&IndexType::BloomFilterIndex)
413 }
414
415 #[cfg(feature = "vector_index")]
417 pub fn vector_index_available(&self) -> bool {
418 self.available_indexes.contains(&IndexType::VectorIndex)
419 }
420
421 pub fn index_file_size(&self) -> u64 {
422 self.index_file_size
423 }
424
425 pub fn is_index_consistent_with_region(&self, metadata: &[ColumnMetadata]) -> bool {
427 let id_to_indexes = self
428 .indexes
429 .iter()
430 .map(|index| (index.column_id, index.created_indexes.clone()))
431 .collect::<std::collections::HashMap<_, _>>();
432 for column in metadata {
433 if !column.column_schema.is_indexed() {
434 continue;
435 }
436 if let Some(indexes) = id_to_indexes.get(&column.column_id) {
437 if column.column_schema.is_inverted_indexed()
438 && !indexes.contains(&IndexType::InvertedIndex)
439 {
440 return false;
441 }
442 if column.column_schema.is_fulltext_indexed()
443 && !indexes.contains(&IndexType::FulltextIndex)
444 {
445 return false;
446 }
447 if column.column_schema.is_skipping_indexed()
448 && !indexes.contains(&IndexType::BloomFilterIndex)
449 {
450 return false;
451 }
452 } else {
453 return false;
454 }
455 }
456 true
457 }
458
459 pub fn file_id(&self) -> RegionFileId {
461 RegionFileId::new(self.region_id, self.file_id)
462 }
463
464 pub fn index_id(&self) -> RegionIndexId {
466 RegionIndexId::new(self.file_id(), self.index_version)
467 }
468}
469
470#[derive(Clone)]
472pub struct FileHandle {
473 inner: Arc<FileHandleInner>,
474}
475
476impl fmt::Debug for FileHandle {
477 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 f.debug_struct("FileHandle")
479 .field("meta", self.meta_ref())
480 .field("compacting", &self.compacting())
481 .field("deleted", &self.inner.deleted.load(Ordering::Relaxed))
482 .finish()
483 }
484}
485
486impl FileHandle {
487 pub fn new(meta: FileMeta, file_purger: FilePurgerRef) -> FileHandle {
488 let pk_range = meta.primary_key_range();
489 FileHandle {
490 inner: Arc::new(FileHandleInner::new(meta, file_purger, pk_range)),
491 }
492 }
493
494 #[cfg(test)]
495 pub fn new_with_primary_key_range(
496 meta: FileMeta,
497 file_purger: FilePurgerRef,
498 primary_key_range: Option<(Bytes, Bytes)>,
499 ) -> FileHandle {
500 FileHandle {
501 inner: Arc::new(FileHandleInner::new(meta, file_purger, primary_key_range)),
502 }
503 }
504
505 pub fn region_id(&self) -> RegionId {
507 self.inner.meta.region_id
508 }
509
510 pub fn file_id(&self) -> RegionFileId {
512 RegionFileId::new(self.inner.meta.region_id, self.inner.meta.file_id)
513 }
514
515 pub fn index_id(&self) -> RegionIndexId {
517 RegionIndexId::new(self.file_id(), self.inner.meta.index_version)
518 }
519
520 pub fn file_path(&self, table_dir: &str, path_type: PathType) -> String {
522 location::sst_file_path(table_dir, self.file_id(), path_type)
523 }
524
525 pub fn time_range(&self) -> FileTimeRange {
527 self.inner.meta.time_range
528 }
529
530 pub fn mark_deleted(&self) {
532 self.inner.deleted.store(true, Ordering::Relaxed);
533 }
534
535 pub fn compacting(&self) -> bool {
536 self.inner.compacting.load(Ordering::Relaxed)
537 }
538
539 pub fn set_compacting(&self, compacting: bool) {
540 self.inner.compacting.store(compacting, Ordering::Relaxed);
541 }
542
543 pub fn try_set_compacting(&self) -> bool {
545 self.inner
546 .compacting
547 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
548 .is_ok()
549 }
550
551 pub fn index_outdated(&self) -> bool {
552 self.inner.index_outdated.load(Ordering::Relaxed)
553 }
554
555 pub fn set_index_outdated(&self, index_outdated: bool) {
556 self.inner
557 .index_outdated
558 .store(index_outdated, Ordering::Relaxed);
559 }
560
561 pub fn meta_ref(&self) -> &FileMeta {
563 &self.inner.meta
564 }
565
566 pub fn file_purger(&self) -> FilePurgerRef {
567 self.inner.file_purger.clone()
568 }
569
570 pub fn size(&self) -> u64 {
571 self.inner.meta.file_size
572 }
573
574 pub fn index_size(&self) -> u64 {
575 self.inner.meta.index_file_size
576 }
577
578 pub fn num_rows(&self) -> usize {
579 self.inner.meta.num_rows as usize
580 }
581
582 pub fn level(&self) -> Level {
583 self.inner.meta.level
584 }
585
586 pub fn is_deleted(&self) -> bool {
587 self.inner.deleted.load(Ordering::Relaxed)
588 }
589
590 pub fn primary_key_range(&self) -> Option<(Bytes, Bytes)> {
591 self.inner.primary_key_range.read().unwrap().clone()
592 }
593
594 pub(crate) fn set_primary_key_range(&self, primary_key_range: (Bytes, Bytes)) {
595 *self.inner.primary_key_range.write().unwrap() = Some(primary_key_range);
596 }
597}
598
599struct FileHandleInner {
603 meta: FileMeta,
604 compacting: AtomicBool,
605 deleted: AtomicBool,
606 index_outdated: AtomicBool,
607 primary_key_range: RwLock<Option<(Bytes, Bytes)>>,
608 file_purger: FilePurgerRef,
609}
610
611impl Drop for FileHandleInner {
612 fn drop(&mut self) {
613 self.file_purger.remove_file(
614 self.meta.clone(),
615 self.deleted.load(Ordering::Acquire),
616 self.index_outdated.load(Ordering::Acquire),
617 );
618 }
619}
620
621impl FileHandleInner {
622 fn new(
624 meta: FileMeta,
625 file_purger: FilePurgerRef,
626 primary_key_range: Option<(Bytes, Bytes)>,
627 ) -> FileHandleInner {
628 file_purger.new_file(&meta);
629 FileHandleInner {
630 meta,
631 compacting: AtomicBool::new(false),
632 deleted: AtomicBool::new(false),
633 index_outdated: AtomicBool::new(false),
634 primary_key_range: RwLock::new(primary_key_range),
635 file_purger,
636 }
637 }
638}
639
640pub async fn delete_files(
647 region_id: RegionId,
648 file_ids: &[(FileId, u64)],
649 delete_index: bool,
650 access_layer: &AccessLayerRef,
651 cache_manager: &Option<CacheManagerRef>,
652) -> crate::error::Result<()> {
653 if let Some(cache) = &cache_manager {
655 for (file_id, _) in file_ids {
656 cache.remove_parquet_meta_data(RegionFileId::new(region_id, *file_id));
657 }
658 }
659 let mut attempted_files = Vec::with_capacity(file_ids.len());
660 let mut index_ids = Vec::new();
661
662 for (file_id, index_version) in file_ids {
663 let region_file_id = RegionFileId::new(region_id, *file_id);
664 attempted_files.push(*file_id);
665 index_ids.extend(
666 (0..=*index_version).map(|version| RegionIndexId::new(region_file_id, version)),
667 );
668 }
669
670 access_layer
671 .delete_ssts(region_id, &attempted_files)
672 .await?;
673 access_layer.delete_indexes(&index_ids).await?;
674
675 debug!(
676 "Attempted to delete {} files for region {}: {:?}",
677 attempted_files.len(),
678 region_id,
679 attempted_files
680 );
681
682 for (file_id, index_version) in file_ids {
683 purge_index_cache_stager(
684 region_id,
685 delete_index,
686 access_layer,
687 cache_manager,
688 *file_id,
689 *index_version,
690 )
691 .await;
692 }
693 Ok(())
694}
695
696#[derive(Clone)]
701pub(crate) struct UncommittedSsts {
702 region_id: RegionId,
703 files: Arc<Mutex<HashMap<FileId, (u64, bool)>>>,
704 access_layer: AccessLayerRef,
705 cache_manager: Option<CacheManagerRef>,
706}
707
708impl UncommittedSsts {
709 pub(crate) fn new(
710 region_id: RegionId,
711 access_layer: AccessLayerRef,
712 cache_manager: Option<CacheManagerRef>,
713 ) -> Self {
714 Self {
715 region_id,
716 files: Arc::new(Mutex::new(HashMap::new())),
717 access_layer,
718 cache_manager,
719 }
720 }
721
722 pub(crate) fn track(&self, ssts: &[SstInfo]) {
724 let mut files = self.files.lock().unwrap();
725 for sst in ssts {
726 files.insert(
727 sst.file_id,
728 (sst.index_metadata.version, sst.index_metadata.file_size > 0),
729 );
730 }
731 }
732
733 pub(crate) fn disarm_cleanup(&self) {
738 self.files.lock().unwrap().clear();
739 }
740
741 #[cfg(test)]
742 pub(crate) fn num_tracked_files(&self) -> usize {
743 self.files.lock().unwrap().len()
744 }
745
746 pub(crate) async fn cleanup(&self) {
748 if let Err(err) = self.try_cleanup().await {
749 error!(err; "Failed to clean uncommitted SSTs for region {}", self.region_id);
750 }
751 }
752
753 async fn try_cleanup(&self) -> crate::error::Result<()> {
754 let files = std::mem::take(&mut *self.files.lock().unwrap());
755 if files.is_empty() {
756 return Ok(());
757 }
758
759 let delete_index = files.values().any(|(_, exists_index)| *exists_index);
760 let file_ids = files
761 .into_iter()
762 .map(|(file_id, (index_version, _))| (file_id, index_version))
763 .collect::<Vec<_>>();
764 delete_files(
765 self.region_id,
766 &file_ids,
767 delete_index,
768 &self.access_layer,
769 &self.cache_manager,
770 )
771 .await
772 }
773
774 #[cfg(test)]
775 pub(crate) async fn cleanup_for_test(&self) -> crate::error::Result<()> {
776 self.try_cleanup().await
777 }
778}
779
780pub async fn delete_index(
781 region_index_id: RegionIndexId,
782 access_layer: &AccessLayerRef,
783 cache_manager: &Option<CacheManagerRef>,
784) -> crate::error::Result<()> {
785 delete_index_and_purge(region_index_id, access_layer, cache_manager).await?;
786
787 Ok(())
788}
789
790pub async fn delete_indexes(
791 index_ids: &[RegionIndexId],
792 access_layer: &AccessLayerRef,
793 cache_manager: &Option<CacheManagerRef>,
794) -> crate::error::Result<()> {
795 if index_ids.is_empty() {
796 return Ok(());
797 }
798
799 if let Err(e) = access_layer.delete_indexes(index_ids).await {
800 error!(e; "Failed to batch delete index files");
801
802 for index_id in index_ids {
803 delete_index_and_purge(*index_id, access_layer, cache_manager).await?;
804 }
805
806 return Ok(());
807 }
808
809 purge_indexes(index_ids, access_layer, cache_manager).await;
810
811 Ok(())
812}
813
814async fn delete_index_and_purge(
815 index_id: RegionIndexId,
816 access_layer: &AccessLayerRef,
817 cache_manager: &Option<CacheManagerRef>,
818) -> crate::error::Result<()> {
819 access_layer.delete_index(index_id).await?;
820 purge_index_cache_stager(
821 index_id.region_id(),
822 true,
823 access_layer,
824 cache_manager,
825 index_id.file_id(),
826 index_id.version,
827 )
828 .await;
829 Ok(())
830}
831
832async fn purge_indexes(
833 index_ids: &[RegionIndexId],
834 access_layer: &AccessLayerRef,
835 cache_manager: &Option<CacheManagerRef>,
836) {
837 for index_id in index_ids {
838 purge_index_cache_stager(
839 index_id.region_id(),
840 true,
841 access_layer,
842 cache_manager,
843 index_id.file_id(),
844 index_id.version,
845 )
846 .await;
847 }
848}
849
850async fn purge_index_cache_stager(
851 region_id: RegionId,
852 delete_index: bool,
853 access_layer: &AccessLayerRef,
854 cache_manager: &Option<CacheManagerRef>,
855 file_id: FileId,
856 index_version: u64,
857) {
858 if let Some(write_cache) = cache_manager.as_ref().and_then(|cache| cache.write_cache()) {
859 if delete_index {
861 write_cache
862 .remove(IndexKey::new(
863 region_id,
864 file_id,
865 FileType::Puffin(index_version),
866 ))
867 .await;
868 }
869
870 write_cache
872 .remove(IndexKey::new(region_id, file_id, FileType::Parquet))
873 .await;
874 }
875
876 if let Err(e) = access_layer
878 .puffin_manager_factory()
879 .purge_stager(RegionIndexId::new(
880 RegionFileId::new(region_id, file_id),
881 index_version,
882 ))
883 .await
884 {
885 error!(e; "Failed to purge stager with index file, file_id: {}, index_version: {}, region: {}",
886 file_id, index_version, region_id);
887 }
888}
889
890#[cfg(test)]
891mod tests {
892 use std::str::FromStr;
893
894 use datatypes::prelude::ConcreteDataType;
895 use datatypes::schema::{
896 ColumnSchema, FulltextAnalyzer, FulltextBackend, FulltextOptions, SkippingIndexOptions,
897 };
898 use datatypes::value::Value;
899 use partition::expr::{PartitionExpr, col};
900
901 use super::*;
902
903 fn create_file_meta(file_id: FileId, level: Level) -> FileMeta {
904 FileMeta {
905 region_id: 0.into(),
906 file_id,
907 time_range: FileTimeRange::default(),
908 level,
909 file_size: 0,
910 max_row_group_uncompressed_size: 0,
911 available_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
912 indexes: vec![ColumnIndexMetadata {
913 column_id: 0,
914 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
915 }],
916 index_file_size: 0,
917 index_version: 0,
918 num_rows: 0,
919 num_row_groups: 0,
920 sequence: None,
921 partition_expr: None,
922 num_series: 0,
923 ..Default::default()
924 }
925 }
926
927 #[test]
928 fn test_deserialize_file_meta() {
929 let file_meta = create_file_meta(FileId::random(), 0);
930 let serialized_file_meta = serde_json::to_string(&file_meta).unwrap();
931 let deserialized_file_meta = serde_json::from_str(&serialized_file_meta);
932 assert_eq!(file_meta, deserialized_file_meta.unwrap());
933 }
934
935 #[test]
936 fn test_deserialize_from_string() {
937 let json_file_meta = "{\"region_id\":0,\"file_id\":\"bc5896ec-e4d8-4017-a80d-f2de73188d55\",\
938 \"time_range\":[{\"value\":0,\"unit\":\"Millisecond\"},{\"value\":0,\"unit\":\"Millisecond\"}],\
939 \"available_indexes\":[\"InvertedIndex\"],\"indexes\":[{\"column_id\": 0, \"created_indexes\": [\"InvertedIndex\"]}],\"level\":0}";
940 let file_meta = create_file_meta(
941 FileId::from_str("bc5896ec-e4d8-4017-a80d-f2de73188d55").unwrap(),
942 0,
943 );
944 let deserialized_file_meta: FileMeta = serde_json::from_str(json_file_meta).unwrap();
945 assert_eq!(file_meta, deserialized_file_meta);
946 }
947
948 #[test]
949 fn test_file_meta_with_partition_expr() {
950 let file_id = FileId::random();
951 let partition_expr = PartitionExpr::new(
952 col("a"),
953 partition::expr::RestrictedOp::GtEq,
954 Value::UInt32(10).into(),
955 );
956
957 let file_meta_with_partition = FileMeta {
958 region_id: 0.into(),
959 file_id,
960 time_range: FileTimeRange::default(),
961 level: 0,
962 file_size: 0,
963 max_row_group_uncompressed_size: 0,
964 available_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
965 indexes: vec![ColumnIndexMetadata {
966 column_id: 0,
967 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
968 }],
969 index_file_size: 0,
970 index_version: 0,
971 num_rows: 0,
972 num_row_groups: 0,
973 sequence: None,
974 partition_expr: Some(partition_expr.clone()),
975 num_series: 0,
976 ..Default::default()
977 };
978
979 let serialized = serde_json::to_string(&file_meta_with_partition).unwrap();
981 let deserialized: FileMeta = serde_json::from_str(&serialized).unwrap();
982 assert_eq!(file_meta_with_partition, deserialized);
983
984 let serialized_value: serde_json::Value = serde_json::from_str(&serialized).unwrap();
986 assert!(serialized_value["partition_expr"].as_str().is_some());
987 let partition_expr_json = serialized_value["partition_expr"].as_str().unwrap();
988 assert!(partition_expr_json.contains("\"Column\":\"a\""));
989 assert!(partition_expr_json.contains("\"op\":\"GtEq\""));
990
991 let file_meta_none = FileMeta {
993 partition_expr: None,
994 ..file_meta_with_partition.clone()
995 };
996 let serialized_none = serde_json::to_string(&file_meta_none).unwrap();
997 let deserialized_none: FileMeta = serde_json::from_str(&serialized_none).unwrap();
998 assert_eq!(file_meta_none, deserialized_none);
999 }
1000
1001 #[test]
1002 fn test_file_meta_partition_expr_backward_compatibility() {
1003 let json_with_partition_expr = r#"{
1005 "region_id": 0,
1006 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1007 "time_range": [
1008 {"value": 0, "unit": "Millisecond"},
1009 {"value": 0, "unit": "Millisecond"}
1010 ],
1011 "level": 0,
1012 "file_size": 0,
1013 "available_indexes": ["InvertedIndex"],
1014 "index_file_size": 0,
1015 "num_rows": 0,
1016 "num_row_groups": 0,
1017 "sequence": null,
1018 "partition_expr": "{\"Expr\":{\"lhs\":{\"Column\":\"a\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"UInt32\":10}}}}"
1019 }"#;
1020
1021 let file_meta: FileMeta = serde_json::from_str(json_with_partition_expr).unwrap();
1022 assert!(file_meta.partition_expr.is_some());
1023 let expr = file_meta.partition_expr.unwrap();
1024 assert_eq!(format!("{}", expr), "a >= 10");
1025
1026 let json_with_empty_expr = r#"{
1028 "region_id": 0,
1029 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1030 "time_range": [
1031 {"value": 0, "unit": "Millisecond"},
1032 {"value": 0, "unit": "Millisecond"}
1033 ],
1034 "level": 0,
1035 "file_size": 0,
1036 "available_indexes": [],
1037 "index_file_size": 0,
1038 "num_rows": 0,
1039 "num_row_groups": 0,
1040 "sequence": null,
1041 "partition_expr": ""
1042 }"#;
1043
1044 let file_meta_empty: FileMeta = serde_json::from_str(json_with_empty_expr).unwrap();
1045 assert!(file_meta_empty.partition_expr.is_none());
1046
1047 let json_with_null_expr = r#"{
1049 "region_id": 0,
1050 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1051 "time_range": [
1052 {"value": 0, "unit": "Millisecond"},
1053 {"value": 0, "unit": "Millisecond"}
1054 ],
1055 "level": 0,
1056 "file_size": 0,
1057 "available_indexes": [],
1058 "index_file_size": 0,
1059 "num_rows": 0,
1060 "num_row_groups": 0,
1061 "sequence": null,
1062 "partition_expr": null
1063 }"#;
1064
1065 let file_meta_null: FileMeta = serde_json::from_str(json_with_null_expr).unwrap();
1066 assert!(file_meta_null.partition_expr.is_none());
1067
1068 let json_with_empty_expr = r#"{
1070 "region_id": 0,
1071 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1072 "time_range": [
1073 {"value": 0, "unit": "Millisecond"},
1074 {"value": 0, "unit": "Millisecond"}
1075 ],
1076 "level": 0,
1077 "file_size": 0,
1078 "available_indexes": [],
1079 "index_file_size": 0,
1080 "num_rows": 0,
1081 "num_row_groups": 0,
1082 "sequence": null
1083 }"#;
1084
1085 let file_meta_empty: FileMeta = serde_json::from_str(json_with_empty_expr).unwrap();
1086 assert!(file_meta_empty.partition_expr.is_none());
1087 }
1088
1089 #[test]
1090 fn test_file_meta_indexes_backward_compatibility() {
1091 let json_old_file_meta = r#"{
1093 "region_id": 0,
1094 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1095 "time_range": [
1096 {"value": 0, "unit": "Millisecond"},
1097 {"value": 0, "unit": "Millisecond"}
1098 ],
1099 "available_indexes": ["InvertedIndex"],
1100 "level": 0,
1101 "file_size": 0,
1102 "index_file_size": 0,
1103 "num_rows": 0,
1104 "num_row_groups": 0
1105 }"#;
1106
1107 let deserialized_file_meta: FileMeta = serde_json::from_str(json_old_file_meta).unwrap();
1108
1109 assert_eq!(deserialized_file_meta.indexes, vec![]);
1111
1112 let expected_indexes: IndexTypes = SmallVec::from_iter([IndexType::InvertedIndex]);
1113 assert_eq!(deserialized_file_meta.available_indexes, expected_indexes);
1114
1115 assert_eq!(
1116 deserialized_file_meta.file_id,
1117 FileId::from_str("bc5896ec-e4d8-4017-a80d-f2de73188d55").unwrap()
1118 );
1119 }
1120 #[test]
1121 fn test_is_index_consistent_with_region() {
1122 fn new_column_meta(
1123 id: ColumnId,
1124 name: &str,
1125 inverted: bool,
1126 fulltext: bool,
1127 skipping: bool,
1128 ) -> ColumnMetadata {
1129 let mut column_schema =
1130 ColumnSchema::new(name, ConcreteDataType::string_datatype(), true);
1131 if inverted {
1132 column_schema = column_schema.with_inverted_index(true);
1133 }
1134 if fulltext {
1135 column_schema = column_schema
1136 .with_fulltext_options(FulltextOptions::new_unchecked(
1137 true,
1138 FulltextAnalyzer::English,
1139 false,
1140 FulltextBackend::Bloom,
1141 1000,
1142 0.01,
1143 ))
1144 .unwrap();
1145 }
1146 if skipping {
1147 column_schema = column_schema
1148 .with_skipping_options(SkippingIndexOptions::new_unchecked(
1149 1024,
1150 0.01,
1151 datatypes::schema::SkippingIndexType::BloomFilter,
1152 ))
1153 .unwrap();
1154 }
1155
1156 ColumnMetadata {
1157 column_schema,
1158 semantic_type: api::v1::SemanticType::Tag,
1159 column_id: id,
1160 }
1161 }
1162
1163 let mut file_meta = FileMeta {
1165 indexes: vec![ColumnIndexMetadata {
1166 column_id: 1,
1167 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1168 }],
1169 ..Default::default()
1170 };
1171 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1172 assert!(file_meta.is_index_consistent_with_region(®ion_meta));
1173
1174 file_meta.indexes = vec![ColumnIndexMetadata {
1176 column_id: 1,
1177 created_indexes: SmallVec::from_iter([
1178 IndexType::InvertedIndex,
1179 IndexType::BloomFilterIndex,
1180 ]),
1181 }];
1182 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1183 assert!(file_meta.is_index_consistent_with_region(®ion_meta));
1184
1185 file_meta.indexes = vec![ColumnIndexMetadata {
1187 column_id: 1,
1188 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1189 }];
1190 let region_meta = vec![new_column_meta(1, "tag1", true, true, false)]; assert!(!file_meta.is_index_consistent_with_region(®ion_meta));
1192
1193 file_meta.indexes = vec![ColumnIndexMetadata {
1195 column_id: 2, created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1197 }];
1198 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)]; assert!(!file_meta.is_index_consistent_with_region(®ion_meta));
1200
1201 file_meta.indexes = vec![ColumnIndexMetadata {
1203 column_id: 1,
1204 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1205 }];
1206 let region_meta = vec![new_column_meta(1, "tag1", false, false, false)]; assert!(file_meta.is_index_consistent_with_region(®ion_meta));
1208
1209 file_meta.indexes = vec![];
1211 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1212 assert!(!file_meta.is_index_consistent_with_region(®ion_meta));
1213
1214 file_meta.indexes = vec![
1216 ColumnIndexMetadata {
1217 column_id: 1,
1218 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1219 },
1220 ColumnIndexMetadata {
1221 column_id: 2, created_indexes: SmallVec::from_iter([IndexType::FulltextIndex]),
1223 },
1224 ];
1225 let region_meta = vec![
1226 new_column_meta(1, "tag1", true, false, false),
1227 new_column_meta(2, "tag2", false, true, true), ];
1229 assert!(!file_meta.is_index_consistent_with_region(®ion_meta));
1230 }
1231}