1use std::convert::TryFrom;
16use std::sync::Arc;
17
18use common_base::range_read::RangeReader;
19use common_telemetry::warn;
20use greptime_proto::v1::index::{BloomFilterMeta, InvertedIndexMeta, InvertedIndexMetas};
21use index::bitmap::BitmapType;
22use index::bloom_filter::reader::{BloomFilterReader, BloomFilterReaderImpl};
23use index::fulltext_index::Config as FulltextConfig;
24use index::inverted_index::format::reader::{InvertedIndexBlobReader, InvertedIndexReader};
25use index::target::IndexTarget;
26use puffin::blob_metadata::BlobMetadata;
27use puffin::puffin_manager::{PuffinManager, PuffinReader};
28use serde_json::{Map, Value, json};
29use store_api::sst_entry::{
30 PUFFIN_INDEX_TYPE_BLOOM_FILTER, PUFFIN_INDEX_TYPE_FULLTEXT_BLOOM,
31 PUFFIN_INDEX_TYPE_FULLTEXT_TANTIVY, PUFFIN_INDEX_TYPE_INVERTED, PuffinIndexMetaEntry,
32};
33use store_api::storage::{ColumnId, RegionGroup, RegionId, RegionNumber, RegionSeq, TableId};
34
35use crate::cache::index::bloom_filter_index::{
36 BloomFilterIndexCacheRef, CachedBloomFilterIndexBlobReader, Tag,
37};
38use crate::cache::index::inverted_index::{CachedInvertedIndexBlobReader, InvertedIndexCacheRef};
39use crate::sst::file::RegionIndexId;
40use crate::sst::index::bloom_filter::INDEX_BLOB_TYPE as BLOOM_BLOB_TYPE;
41use crate::sst::index::fulltext_index::{
42 FULLTEXT_BLOOM_VERSION, INDEX_BLOB_TYPE_BLOOM as FULLTEXT_BLOOM_BLOB_TYPE,
43 INDEX_BLOB_TYPE_BLOOM_V1 as FULLTEXT_BLOOM_V1_BLOB_TYPE,
44 INDEX_BLOB_TYPE_TANTIVY as FULLTEXT_TANTIVY_BLOB_TYPE,
45};
46use crate::sst::index::inverted_index::INDEX_BLOB_TYPE as INVERTED_BLOB_TYPE;
47use crate::sst::index::puffin_manager::{SstPuffinManager, SstPuffinReader};
48
49const TARGET_TYPE_UNKNOWN: &str = "unknown";
50
51const TARGET_TYPE_COLUMN: &str = "column";
52
53const TARGET_TYPE_JSON_PATH: &str = "json_path";
54
55pub(crate) struct IndexEntryContext<'a> {
56 pub(crate) table_dir: &'a str,
57 pub(crate) index_file_path: &'a str,
58 pub(crate) region_id: RegionId,
59 pub(crate) table_id: TableId,
60 pub(crate) region_number: RegionNumber,
61 pub(crate) region_group: RegionGroup,
62 pub(crate) region_sequence: RegionSeq,
63 pub(crate) file_id: &'a str,
64 pub(crate) index_file_size: Option<u64>,
65 pub(crate) node_id: Option<u64>,
66}
67
68pub(crate) async fn collect_index_entries_from_puffin(
70 manager: SstPuffinManager,
71 region_index_id: RegionIndexId,
72 context: IndexEntryContext<'_>,
73 bloom_filter_cache: Option<BloomFilterIndexCacheRef>,
74 inverted_index_cache: Option<InvertedIndexCacheRef>,
75) -> Vec<PuffinIndexMetaEntry> {
76 let mut entries = Vec::new();
77
78 let reader = match manager.reader(®ion_index_id).await {
79 Ok(reader) => reader,
80 Err(err) => {
81 warn!(
82 err;
83 "Failed to open puffin index file, table_dir: {}, file_id: {}",
84 context.table_dir,
85 context.file_id
86 );
87 return entries;
88 }
89 };
90
91 let file_metadata = match reader.metadata().await {
92 Ok(metadata) => metadata,
93 Err(err) => {
94 warn!(
95 err;
96 "Failed to read puffin file metadata, table_dir: {}, file_id: {}",
97 context.table_dir,
98 context.file_id
99 );
100 return entries;
101 }
102 };
103
104 for blob in &file_metadata.blobs {
105 match BlobIndexTypeTargetKey::from_blob_type(&blob.blob_type) {
106 Some(BlobIndexTypeTargetKey::BloomFilter(target_key)) => {
107 let bloom_meta = try_read_bloom_meta(
108 &reader,
109 region_index_id,
110 blob.blob_type.as_str(),
111 target_key,
112 bloom_filter_cache.as_ref(),
113 Tag::Skipping,
114 &context,
115 )
116 .await;
117
118 let bloom_value = bloom_meta.as_deref().map(bloom_meta_value);
119 let (target_type, target_json) = decode_target_info(target_key);
120 let meta_json = build_meta_json(bloom_value, None, None);
121 let entry = build_index_entry(
122 &context,
123 PUFFIN_INDEX_TYPE_BLOOM_FILTER,
124 target_type,
125 target_key.to_string(),
126 target_json,
127 blob.length as u64,
128 meta_json,
129 );
130 entries.push(entry);
131 }
132 Some(BlobIndexTypeTargetKey::FulltextBloom(target_key, bloom_version)) => {
133 let bloom_meta = try_read_bloom_meta(
134 &reader,
135 region_index_id,
136 blob.blob_type.as_str(),
137 target_key,
138 bloom_filter_cache.as_ref(),
139 Tag::Fulltext,
140 &context,
141 )
142 .await;
143
144 let bloom_value = bloom_meta.as_deref().map(bloom_meta_value);
145 let fulltext_value = Some(fulltext_meta_value(blob, Some(bloom_version)));
146 let (target_type, target_json) = decode_target_info(target_key);
147 let meta_json = build_meta_json(bloom_value, fulltext_value, None);
148 let entry = build_index_entry(
149 &context,
150 PUFFIN_INDEX_TYPE_FULLTEXT_BLOOM,
151 target_type,
152 target_key.to_string(),
153 target_json,
154 blob.length as u64,
155 meta_json,
156 );
157 entries.push(entry);
158 }
159 Some(BlobIndexTypeTargetKey::FulltextTantivy(target_key)) => {
160 let fulltext_value = Some(fulltext_meta_value(blob, None));
161 let (target_type, target_json) = decode_target_info(target_key);
162 let meta_json = build_meta_json(None, fulltext_value, None);
163 let entry = build_index_entry(
164 &context,
165 PUFFIN_INDEX_TYPE_FULLTEXT_TANTIVY,
166 target_type,
167 target_key.to_string(),
168 target_json,
169 blob.length as u64,
170 meta_json,
171 );
172 entries.push(entry);
173 }
174 Some(BlobIndexTypeTargetKey::Inverted) => {
175 let mut inverted_entries = collect_inverted_entries(
176 &reader,
177 region_index_id,
178 inverted_index_cache.as_ref(),
179 &context,
180 )
181 .await;
182 entries.append(&mut inverted_entries);
183 }
184 None => {}
185 }
186 }
187
188 entries
189}
190
191async fn collect_inverted_entries(
192 reader: &SstPuffinReader,
193 region_index_id: RegionIndexId,
194 cache: Option<&InvertedIndexCacheRef>,
195 context: &IndexEntryContext<'_>,
196) -> Vec<PuffinIndexMetaEntry> {
197 let file_id = region_index_id.file_id();
199
200 let guard = match reader.blob(INVERTED_BLOB_TYPE).await {
201 Ok(guard) => guard,
202 Err(err) => {
203 warn!(
204 err;
205 "Failed to open inverted index blob, table_dir: {}, file_id: {}",
206 context.table_dir,
207 context.file_id
208 );
209 return Vec::new();
210 }
211 };
212
213 let blob_reader = match guard.reader().await {
214 Ok(reader) => reader,
215 Err(err) => {
216 warn!(
217 err;
218 "Failed to build inverted index blob reader, table_dir: {}, file_id: {}",
219 context.table_dir,
220 context.file_id
221 );
222 return Vec::new();
223 }
224 };
225
226 let blob_size = blob_reader
227 .metadata()
228 .await
229 .ok()
230 .map(|meta| meta.content_length);
231 let metas = if let (Some(cache), Some(blob_size)) = (cache, blob_size) {
232 let reader = CachedInvertedIndexBlobReader::new(
233 file_id,
234 region_index_id.version,
235 blob_size,
236 InvertedIndexBlobReader::new(blob_reader),
237 cache.clone(),
238 );
239 match reader.metadata(None).await {
240 Ok(metas) => metas,
241 Err(err) => {
242 warn!(
243 err;
244 "Failed to read inverted index metadata, table_dir: {}, file_id: {}",
245 context.table_dir,
246 context.file_id
247 );
248 return Vec::new();
249 }
250 }
251 } else {
252 let reader = InvertedIndexBlobReader::new(blob_reader);
253 match reader.metadata(None).await {
254 Ok(metas) => metas,
255 Err(err) => {
256 warn!(
257 err;
258 "Failed to read inverted index metadata, table_dir: {}, file_id: {}",
259 context.table_dir,
260 context.file_id
261 );
262 return Vec::new();
263 }
264 }
265 };
266
267 build_inverted_entries(context, metas.as_ref())
268}
269
270fn build_inverted_entries(
271 context: &IndexEntryContext<'_>,
272 metas: &InvertedIndexMetas,
273) -> Vec<PuffinIndexMetaEntry> {
274 let mut entries = Vec::new();
275 for (name, meta) in &metas.metas {
276 let (target_type, target_json) = decode_target_info(name);
277 let inverted_value = inverted_meta_value(meta, metas);
278 let meta_json = build_meta_json(None, None, Some(inverted_value));
279 let entry = build_index_entry(
280 context,
281 PUFFIN_INDEX_TYPE_INVERTED,
282 target_type,
283 name.clone(),
284 target_json,
285 meta.inverted_index_size,
286 meta_json,
287 );
288 entries.push(entry);
289 }
290 entries
291}
292
293async fn try_read_bloom_meta(
294 reader: &SstPuffinReader,
295 region_index_id: RegionIndexId,
296 blob_type: &str,
297 target_key: &str,
298 cache: Option<&BloomFilterIndexCacheRef>,
299 tag: Tag,
300 context: &IndexEntryContext<'_>,
301) -> Option<Arc<BloomFilterMeta>> {
302 let column_id = decode_column_id(target_key);
303
304 match reader.blob(blob_type).await {
306 Ok(guard) => match guard.reader().await {
307 Ok(blob_reader) => {
308 let blob_size = blob_reader
309 .metadata()
310 .await
311 .ok()
312 .map(|meta| meta.content_length);
313 let bloom_reader = BloomFilterReaderImpl::new(blob_reader);
314 let result = match (cache, column_id, blob_size) {
315 (Some(cache), Some(column_id), Some(blob_size)) => {
316 CachedBloomFilterIndexBlobReader::new(
317 region_index_id.file_id(),
318 region_index_id.version,
319 IndexTarget::ColumnId(column_id),
320 tag,
321 blob_size,
322 bloom_reader,
323 cache.clone(),
324 )
325 .metadata(None)
326 .await
327 }
328 _ => bloom_reader.metadata(None).await,
329 };
330
331 match result {
332 Ok(meta) => Some(meta),
333 Err(err) => {
334 warn!(
335 err;
336 "Failed to read index metadata, table_dir: {}, file_id: {}, blob: {}",
337 context.table_dir,
338 context.file_id,
339 blob_type
340 );
341 None
342 }
343 }
344 }
345 Err(err) => {
346 warn!(
347 err;
348 "Failed to open index blob reader, table_dir: {}, file_id: {}, blob: {}",
349 context.table_dir,
350 context.file_id,
351 blob_type
352 );
353 None
354 }
355 },
356 Err(err) => {
357 warn!(
358 err;
359 "Failed to open index blob, table_dir: {}, file_id: {}, blob: {}",
360 context.table_dir,
361 context.file_id,
362 blob_type
363 );
364 None
365 }
366 }
367}
368
369fn decode_target_info(target_key: &str) -> (String, String) {
370 match IndexTarget::decode(target_key) {
371 Ok(IndexTarget::ColumnId(id)) => (
372 TARGET_TYPE_COLUMN.to_string(),
373 json!({ "column": id }).to_string(),
374 ),
375 Ok(IndexTarget::JsonPath(target)) => (
376 TARGET_TYPE_JSON_PATH.to_string(),
377 json!({ "json_path": { "column": target.column_id(), "path": target.path() } })
378 .to_string(),
379 ),
380 _ => (
381 TARGET_TYPE_UNKNOWN.to_string(),
382 json!({ "error": "failed_to_decode" }).to_string(),
383 ),
384 }
385}
386
387fn decode_column_id(target_key: &str) -> Option<ColumnId> {
388 match IndexTarget::decode(target_key) {
389 Ok(IndexTarget::ColumnId(id)) => Some(id),
390 _ => None,
391 }
392}
393
394fn bloom_meta_value(meta: &BloomFilterMeta) -> Value {
395 json!({
396 "rows_per_segment": meta.rows_per_segment,
397 "segment_count": meta.segment_count,
398 "row_count": meta.row_count,
399 "bloom_filter_size": meta.bloom_filter_size,
400 })
401}
402
403fn fulltext_meta_value(blob: &BlobMetadata, bloom_version: Option<u8>) -> Value {
404 let config = FulltextConfig::from_blob_metadata(blob).unwrap_or_default();
405 let mut value = json!({
406 "analyzer": config.analyzer.to_str(),
407 "case_sensitive": config.case_sensitive,
408 });
409 if let Some(version) = bloom_version {
410 value["bloom_version"] = json!(version);
411 }
412 value
413}
414
415fn inverted_meta_value(meta: &InvertedIndexMeta, metas: &InvertedIndexMetas) -> Value {
416 let bitmap_type = BitmapType::try_from(meta.bitmap_type)
417 .map(|bt| format!("{:?}", bt))
418 .unwrap_or_else(|_| meta.bitmap_type.to_string());
419 json!({
420 "bitmap_type": bitmap_type,
421 "base_offset": meta.base_offset,
422 "inverted_index_size": meta.inverted_index_size,
423 "relative_fst_offset": meta.relative_fst_offset,
424 "fst_size": meta.fst_size,
425 "relative_null_bitmap_offset": meta.relative_null_bitmap_offset,
426 "null_bitmap_size": meta.null_bitmap_size,
427 "segment_row_count": metas.segment_row_count,
428 "total_row_count": metas.total_row_count,
429 })
430}
431
432fn build_meta_json(
433 bloom: Option<Value>,
434 fulltext: Option<Value>,
435 inverted: Option<Value>,
436) -> Option<String> {
437 let mut map = Map::new();
438 if let Some(value) = bloom {
439 map.insert("bloom".to_string(), value);
440 }
441 if let Some(value) = fulltext {
442 map.insert("fulltext".to_string(), value);
443 }
444 if let Some(value) = inverted {
445 map.insert("inverted".to_string(), value);
446 }
447 if map.is_empty() {
448 None
449 } else {
450 Some(Value::Object(map).to_string())
451 }
452}
453
454enum BlobIndexTypeTargetKey<'a> {
455 BloomFilter(&'a str),
456 FulltextBloom(&'a str, u8),
458 FulltextTantivy(&'a str),
459 Inverted,
460}
461
462impl<'a> BlobIndexTypeTargetKey<'a> {
463 fn from_blob_type(blob_type: &'a str) -> Option<Self> {
464 if let Some(target_key) = Self::target_key_from_blob(blob_type, BLOOM_BLOB_TYPE) {
465 Some(BlobIndexTypeTargetKey::BloomFilter(target_key))
466 } else if let Some(target_key) =
467 Self::target_key_from_blob(blob_type, FULLTEXT_BLOOM_BLOB_TYPE)
469 {
470 Some(BlobIndexTypeTargetKey::FulltextBloom(
471 target_key,
472 FULLTEXT_BLOOM_VERSION,
473 ))
474 } else if let Some(target_key) =
475 Self::target_key_from_blob(blob_type, FULLTEXT_BLOOM_V1_BLOB_TYPE)
476 {
477 Some(BlobIndexTypeTargetKey::FulltextBloom(target_key, 1))
478 } else if let Some(target_key) =
479 Self::target_key_from_blob(blob_type, FULLTEXT_TANTIVY_BLOB_TYPE)
480 {
481 Some(BlobIndexTypeTargetKey::FulltextTantivy(target_key))
482 } else if blob_type == INVERTED_BLOB_TYPE {
483 Some(BlobIndexTypeTargetKey::Inverted)
484 } else {
485 None
486 }
487 }
488
489 fn target_key_from_blob(blob_type: &'a str, prefix: &str) -> Option<&'a str> {
490 blob_type
492 .strip_prefix(prefix)
493 .and_then(|suffix| suffix.strip_prefix('-'))
494 }
495}
496
497fn build_index_entry(
498 context: &IndexEntryContext<'_>,
499 index_type: &str,
500 target_type: String,
501 target_key: String,
502 target_json: String,
503 blob_size: u64,
504 meta_json: Option<String>,
505) -> PuffinIndexMetaEntry {
506 PuffinIndexMetaEntry {
507 table_dir: context.table_dir.to_string(),
508 index_file_path: context.index_file_path.to_string(),
509 region_id: context.region_id,
510 table_id: context.table_id,
511 region_number: context.region_number,
512 region_group: context.region_group,
513 region_sequence: context.region_sequence,
514 file_id: context.file_id.to_string(),
515 index_file_size: context.index_file_size,
516 index_type: index_type.to_string(),
517 target_type,
518 target_key,
519 target_json,
520 blob_size,
521 meta_json,
522 node_id: context.node_id,
523 }
524}