1use std::sync::Arc;
16
17use bytes::Bytes;
18use common_recordbatch::DfRecordBatch;
19use common_time::Timestamp;
20use common_time::timestamp::TimeUnit;
21use datafusion_common::DataFusionError;
22use datafusion_expr::{LogicalPlan, LogicalPlanBuilder, LogicalTableSource};
23use datatypes::arrow::array::{
24 ArrayRef, BinaryArray, BooleanArray, TimestampMillisecondArray, TimestampNanosecondArray,
25 UInt8Array, UInt32Array, UInt64Array,
26};
27use datatypes::arrow::datatypes::SchemaRef as ArrowSchemaRef;
28use datatypes::arrow::error::ArrowError;
29use datatypes::arrow_array::StringArray;
30use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
31use serde::{Deserialize, Serialize};
32
33use crate::storage::{RegionGroup, RegionId, RegionNumber, RegionSeq, ScanRequest, TableId};
34
35pub const TABLE_DIR: &str = "table_dir";
37pub const REGION_ID: &str = "region_id";
38pub const TABLE_ID: &str = "table_id";
39pub const REGION_NUMBER: &str = "region_number";
40pub const REGION_GROUP: &str = "region_group";
41pub const REGION_SEQUENCE: &str = "region_sequence";
42pub const FILE_ID: &str = "file_id";
43pub const INDEX_VERSION: &str = "index_version";
44pub const LEVEL: &str = "level";
45pub const FILE_PATH: &str = "file_path";
46pub const FILE_SIZE: &str = "file_size";
47pub const INDEX_FILE_PATH: &str = "index_file_path";
48pub const INDEX_FILE_SIZE: &str = "index_file_size";
49pub const NUM_ROWS: &str = "num_rows";
50pub const NUM_ROW_GROUPS: &str = "num_row_groups";
51pub const NUM_SERIES: &str = "num_series";
52pub const MIN_TS: &str = "min_ts";
53pub const MAX_TS: &str = "max_ts";
54pub const SEQUENCE: &str = "sequence";
55pub const ORIGIN_REGION_ID: &str = "origin_region_id";
56pub const NODE_ID: &str = "node_id";
57pub const VISIBLE: &str = "visible";
58pub const PRIMARY_KEY_MIN: &str = "primary_key_min";
59pub const PRIMARY_KEY_MAX: &str = "primary_key_max";
60pub const MAX_ROW_GROUP_UNCOMPRESSED_SIZE: &str = "max_row_group_uncompressed_size";
61pub const PARTITION_EXPR: &str = "partition_expr";
62
63pub const PUFFIN_INDEX_TYPE_BLOOM_FILTER: &str = "bloom_filter";
65pub const PUFFIN_INDEX_TYPE_FULLTEXT_BLOOM: &str = "fulltext_bloom";
67pub const PUFFIN_INDEX_TYPE_FULLTEXT_TANTIVY: &str = "fulltext_tantivy";
69pub const PUFFIN_INDEX_TYPE_INVERTED: &str = "inverted";
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74pub struct ManifestSstEntry {
75 pub table_dir: String,
77 pub region_id: RegionId,
79 pub table_id: TableId,
81 pub region_number: RegionNumber,
83 pub region_group: RegionGroup,
85 pub region_sequence: RegionSeq,
87 pub file_id: String,
89 pub index_version: u64,
91 pub level: u8,
93 pub file_path: String,
95 pub file_size: u64,
97 pub max_row_group_uncompressed_size: u64,
99 pub index_file_path: Option<String>,
101 pub index_file_size: Option<u64>,
103 pub num_rows: u64,
105 pub num_row_groups: u64,
107 pub num_series: Option<u64>,
109 pub min_ts: Timestamp,
111 pub max_ts: Timestamp,
113 pub sequence: Option<u64>,
115 pub partition_expr: Option<String>,
117 pub origin_region_id: RegionId,
119 pub node_id: Option<u64>,
121 pub visible: bool,
123 pub primary_key_min: Option<Bytes>,
125 pub primary_key_max: Option<Bytes>,
127}
128
129impl ManifestSstEntry {
130 pub fn schema() -> SchemaRef {
132 use datatypes::prelude::ConcreteDataType as Ty;
133 Arc::new(Schema::new(vec![
134 ColumnSchema::new(TABLE_DIR, Ty::string_datatype(), false),
135 ColumnSchema::new(REGION_ID, Ty::uint64_datatype(), false),
136 ColumnSchema::new(TABLE_ID, Ty::uint32_datatype(), false),
137 ColumnSchema::new(REGION_NUMBER, Ty::uint32_datatype(), false),
138 ColumnSchema::new(REGION_GROUP, Ty::uint8_datatype(), false),
139 ColumnSchema::new(REGION_SEQUENCE, Ty::uint32_datatype(), false),
140 ColumnSchema::new(FILE_ID, Ty::string_datatype(), false),
141 ColumnSchema::new(INDEX_VERSION, Ty::uint64_datatype(), false),
142 ColumnSchema::new(LEVEL, Ty::uint8_datatype(), false),
143 ColumnSchema::new(FILE_PATH, Ty::string_datatype(), false),
144 ColumnSchema::new(FILE_SIZE, Ty::uint64_datatype(), false),
145 ColumnSchema::new(INDEX_FILE_PATH, Ty::string_datatype(), true),
146 ColumnSchema::new(INDEX_FILE_SIZE, Ty::uint64_datatype(), true),
147 ColumnSchema::new(NUM_ROWS, Ty::uint64_datatype(), false),
148 ColumnSchema::new(NUM_ROW_GROUPS, Ty::uint64_datatype(), false),
149 ColumnSchema::new(NUM_SERIES, Ty::uint64_datatype(), true),
150 ColumnSchema::new(MIN_TS, Ty::timestamp_nanosecond_datatype(), true),
151 ColumnSchema::new(MAX_TS, Ty::timestamp_nanosecond_datatype(), true),
152 ColumnSchema::new(SEQUENCE, Ty::uint64_datatype(), true),
153 ColumnSchema::new(ORIGIN_REGION_ID, Ty::uint64_datatype(), false),
154 ColumnSchema::new(NODE_ID, Ty::uint64_datatype(), true),
155 ColumnSchema::new(VISIBLE, Ty::boolean_datatype(), false),
156 ColumnSchema::new(PRIMARY_KEY_MIN, Ty::binary_datatype(), true),
157 ColumnSchema::new(PRIMARY_KEY_MAX, Ty::binary_datatype(), true),
158 ColumnSchema::new(
159 MAX_ROW_GROUP_UNCOMPRESSED_SIZE,
160 Ty::uint64_datatype(),
161 false,
162 ),
163 ColumnSchema::new(PARTITION_EXPR, Ty::string_datatype(), true),
164 ]))
165 }
166
167 pub fn to_record_batch(entries: &[Self]) -> std::result::Result<DfRecordBatch, ArrowError> {
169 Self::to_projected_record_batch(entries, None)
170 }
171
172 fn to_projected_record_batch(
174 entries: &[Self],
175 projection: Option<&[usize]>,
176 ) -> std::result::Result<DfRecordBatch, ArrowError> {
177 let schema = Self::schema().arrow_schema().clone();
178 let schema = match projection {
179 Some(projection) => Arc::new(schema.project(projection)?),
180 None => schema,
181 };
182 ManifestSstBatchBuilder::new(schema).build(entries)
183 }
184
185 pub fn reserved_table_name_for_inspection() -> &'static str {
191 "__inspect/__mito/__sst_manifest"
192 }
193
194 pub fn build_plan(scan_request: ScanRequest) -> Result<LogicalPlan, DataFusionError> {
196 build_plan_helper(
197 scan_request,
198 Self::reserved_table_name_for_inspection(),
199 Self::schema(),
200 )
201 }
202}
203
204pub struct ManifestSstBatchBuilder {
207 schema: ArrowSchemaRef,
208 duplicates: Vec<Option<usize>>,
209}
210
211impl ManifestSstBatchBuilder {
212 pub fn new(schema: ArrowSchemaRef) -> Self {
214 let duplicates = schema
215 .fields()
216 .iter()
217 .enumerate()
218 .map(|(index, field)| {
219 schema.fields()[..index]
220 .iter()
221 .position(|previous| previous == field)
222 })
223 .collect();
224 Self { schema, duplicates }
225 }
226
227 pub fn build(
229 &self,
230 entries: &[ManifestSstEntry],
231 ) -> std::result::Result<DfRecordBatch, ArrowError> {
232 let mut columns: Vec<ArrayRef> = Vec::with_capacity(self.schema.fields().len());
233 for (index, field) in self.schema.fields().iter().enumerate() {
234 if let Some(previous) = self.duplicates[index] {
235 columns.push(columns[previous].clone());
236 continue;
237 }
238 let array: ArrayRef = match field.name().as_str() {
239 TABLE_DIR => Arc::new(StringArray::from_iter_values(
240 entries.iter().map(|e| e.table_dir.as_str()),
241 )),
242 REGION_ID => Arc::new(UInt64Array::from_iter_values(
243 entries.iter().map(|e| e.region_id.as_u64()),
244 )),
245 TABLE_ID => Arc::new(UInt32Array::from_iter_values(
246 entries.iter().map(|e| e.table_id),
247 )),
248 REGION_NUMBER => Arc::new(UInt32Array::from_iter_values(
249 entries.iter().map(|e| e.region_number),
250 )),
251 REGION_GROUP => Arc::new(UInt8Array::from_iter_values(
252 entries.iter().map(|e| e.region_group),
253 )),
254 REGION_SEQUENCE => Arc::new(UInt32Array::from_iter_values(
255 entries.iter().map(|e| e.region_sequence),
256 )),
257 FILE_ID => Arc::new(StringArray::from_iter_values(
258 entries.iter().map(|e| e.file_id.as_str()),
259 )),
260 INDEX_VERSION => Arc::new(UInt64Array::from_iter(
261 entries.iter().map(|e| e.index_version),
262 )),
263 LEVEL => Arc::new(UInt8Array::from_iter_values(
264 entries.iter().map(|e| e.level),
265 )),
266 FILE_PATH => Arc::new(StringArray::from_iter_values(
267 entries.iter().map(|e| e.file_path.as_str()),
268 )),
269 FILE_SIZE => Arc::new(UInt64Array::from_iter_values(
270 entries.iter().map(|e| e.file_size),
271 )),
272 INDEX_FILE_PATH => Arc::new(StringArray::from_iter(
273 entries.iter().map(|e| e.index_file_path.as_ref()),
274 )),
275 INDEX_FILE_SIZE => Arc::new(UInt64Array::from_iter(
276 entries.iter().map(|e| e.index_file_size),
277 )),
278 NUM_ROWS => Arc::new(UInt64Array::from_iter_values(
279 entries.iter().map(|e| e.num_rows),
280 )),
281 NUM_ROW_GROUPS => Arc::new(UInt64Array::from_iter_values(
282 entries.iter().map(|e| e.num_row_groups),
283 )),
284 NUM_SERIES => {
285 Arc::new(UInt64Array::from_iter(entries.iter().map(|e| e.num_series)))
286 }
287 MIN_TS => Arc::new(TimestampNanosecondArray::from_iter(entries.iter().map(
288 |e| {
289 e.min_ts
290 .convert_to(TimeUnit::Nanosecond)
291 .map(|ts| ts.value())
292 },
293 ))),
294 MAX_TS => Arc::new(TimestampNanosecondArray::from_iter(entries.iter().map(
295 |e| {
296 e.max_ts
297 .convert_to(TimeUnit::Nanosecond)
298 .map(|ts| ts.value())
299 },
300 ))),
301 SEQUENCE => Arc::new(UInt64Array::from_iter(entries.iter().map(|e| e.sequence))),
302 ORIGIN_REGION_ID => Arc::new(UInt64Array::from_iter_values(
303 entries.iter().map(|e| e.origin_region_id.as_u64()),
304 )),
305 NODE_ID => Arc::new(UInt64Array::from_iter(entries.iter().map(|e| e.node_id))),
306 VISIBLE => Arc::new(BooleanArray::from_iter(
307 entries.iter().map(|e| Some(e.visible)),
308 )),
309 PRIMARY_KEY_MIN => Arc::new(BinaryArray::from_iter(
310 entries.iter().map(|e| e.primary_key_min.as_deref()),
311 )),
312 PRIMARY_KEY_MAX => Arc::new(BinaryArray::from_iter(
313 entries.iter().map(|e| e.primary_key_max.as_deref()),
314 )),
315 MAX_ROW_GROUP_UNCOMPRESSED_SIZE => Arc::new(UInt64Array::from_iter_values(
316 entries.iter().map(|e| e.max_row_group_uncompressed_size),
317 )),
318 PARTITION_EXPR => Arc::new(StringArray::from_iter(
319 entries.iter().map(|e| e.partition_expr.as_ref()),
320 )),
321 name => {
322 return Err(ArrowError::SchemaError(format!(
323 "Unknown manifest SST column: {name}"
324 )));
325 }
326 };
327 columns.push(array);
328 }
329 DfRecordBatch::try_new_with_options(
330 self.schema.clone(),
331 columns,
332 &datatypes::arrow::record_batch::RecordBatchOptions::new()
333 .with_row_count(Some(entries.len())),
334 )
335 }
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
340pub struct StorageSstEntry {
341 pub file_path: String,
343 pub file_size: Option<u64>,
345 pub last_modified_ms: Option<Timestamp>,
347 pub node_id: Option<u64>,
349}
350
351impl StorageSstEntry {
352 pub fn schema() -> SchemaRef {
354 use datatypes::prelude::ConcreteDataType as Ty;
355 Arc::new(Schema::new(vec![
356 ColumnSchema::new("file_path", Ty::string_datatype(), false),
357 ColumnSchema::new("file_size", Ty::uint64_datatype(), true),
358 ColumnSchema::new(
359 "last_modified_ms",
360 Ty::timestamp_millisecond_datatype(),
361 true,
362 ),
363 ColumnSchema::new("node_id", Ty::uint64_datatype(), true),
364 ]))
365 }
366
367 pub fn to_record_batch(entries: &[Self]) -> std::result::Result<DfRecordBatch, ArrowError> {
369 let schema = Self::schema();
370 let file_paths = entries.iter().map(|e| e.file_path.as_str());
371 let file_sizes = entries.iter().map(|e| e.file_size);
372 let last_modified_ms = entries.iter().map(|e| {
373 e.last_modified_ms
374 .and_then(|ts| ts.convert_to(TimeUnit::Millisecond).map(|ts| ts.value()))
375 });
376 let node_ids = entries.iter().map(|e| e.node_id);
377
378 let columns: Vec<ArrayRef> = vec![
379 Arc::new(StringArray::from_iter_values(file_paths)),
380 Arc::new(UInt64Array::from_iter(file_sizes)),
381 Arc::new(TimestampMillisecondArray::from_iter(last_modified_ms)),
382 Arc::new(UInt64Array::from_iter(node_ids)),
383 ];
384
385 DfRecordBatch::try_new(schema.arrow_schema().clone(), columns)
386 }
387
388 pub fn reserved_table_name_for_inspection() -> &'static str {
394 "__inspect/__mito/__sst_storage"
395 }
396
397 pub fn build_plan(scan_request: ScanRequest) -> Result<LogicalPlan, DataFusionError> {
399 build_plan_helper(
400 scan_request,
401 Self::reserved_table_name_for_inspection(),
402 Self::schema(),
403 )
404 }
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409pub struct PuffinIndexMetaEntry {
410 pub table_dir: String,
412 pub index_file_path: String,
414 pub region_id: RegionId,
416 pub table_id: TableId,
418 pub region_number: RegionNumber,
420 pub region_group: RegionGroup,
422 pub region_sequence: RegionSeq,
424 pub file_id: String,
426 pub index_file_size: Option<u64>,
428 pub index_type: String,
430 pub target_type: String,
432 pub target_key: String,
434 pub target_json: String,
436 pub blob_size: u64,
438 pub meta_json: Option<String>,
440 pub node_id: Option<u64>,
442}
443
444impl PuffinIndexMetaEntry {
445 pub fn schema() -> SchemaRef {
447 use datatypes::prelude::ConcreteDataType as Ty;
448 Arc::new(Schema::new(vec![
449 ColumnSchema::new("table_dir", Ty::string_datatype(), false),
450 ColumnSchema::new("index_file_path", Ty::string_datatype(), false),
451 ColumnSchema::new("region_id", Ty::uint64_datatype(), false),
452 ColumnSchema::new("table_id", Ty::uint32_datatype(), false),
453 ColumnSchema::new("region_number", Ty::uint32_datatype(), false),
454 ColumnSchema::new("region_group", Ty::uint8_datatype(), false),
455 ColumnSchema::new("region_sequence", Ty::uint32_datatype(), false),
456 ColumnSchema::new("file_id", Ty::string_datatype(), false),
457 ColumnSchema::new("index_file_size", Ty::uint64_datatype(), true),
458 ColumnSchema::new("index_type", Ty::string_datatype(), false),
459 ColumnSchema::new("target_type", Ty::string_datatype(), false),
460 ColumnSchema::new("target_key", Ty::string_datatype(), false),
461 ColumnSchema::new("target_json", Ty::string_datatype(), false),
462 ColumnSchema::new("blob_size", Ty::uint64_datatype(), false),
463 ColumnSchema::new("meta_json", Ty::string_datatype(), true),
464 ColumnSchema::new("node_id", Ty::uint64_datatype(), true),
465 ]))
466 }
467
468 pub fn to_record_batch(entries: &[Self]) -> std::result::Result<DfRecordBatch, ArrowError> {
470 let schema = Self::schema();
471 let table_dirs = entries.iter().map(|e| e.table_dir.as_str());
472 let index_file_paths = entries.iter().map(|e| e.index_file_path.as_str());
473 let region_ids = entries.iter().map(|e| e.region_id.as_u64());
474 let table_ids = entries.iter().map(|e| e.table_id);
475 let region_numbers = entries.iter().map(|e| e.region_number);
476 let region_groups = entries.iter().map(|e| e.region_group);
477 let region_sequences = entries.iter().map(|e| e.region_sequence);
478 let file_ids = entries.iter().map(|e| e.file_id.as_str());
479 let index_file_sizes = entries.iter().map(|e| e.index_file_size);
480 let index_types = entries.iter().map(|e| e.index_type.as_str());
481 let target_types = entries.iter().map(|e| e.target_type.as_str());
482 let target_keys = entries.iter().map(|e| e.target_key.as_str());
483 let target_jsons = entries.iter().map(|e| e.target_json.as_str());
484 let blob_sizes = entries.iter().map(|e| e.blob_size);
485 let meta_jsons = entries.iter().map(|e| e.meta_json.as_deref());
486 let node_ids = entries.iter().map(|e| e.node_id);
487
488 let columns: Vec<ArrayRef> = vec![
489 Arc::new(StringArray::from_iter_values(table_dirs)),
490 Arc::new(StringArray::from_iter_values(index_file_paths)),
491 Arc::new(UInt64Array::from_iter_values(region_ids)),
492 Arc::new(UInt32Array::from_iter_values(table_ids)),
493 Arc::new(UInt32Array::from_iter_values(region_numbers)),
494 Arc::new(UInt8Array::from_iter_values(region_groups)),
495 Arc::new(UInt32Array::from_iter_values(region_sequences)),
496 Arc::new(StringArray::from_iter_values(file_ids)),
497 Arc::new(UInt64Array::from_iter(index_file_sizes)),
498 Arc::new(StringArray::from_iter_values(index_types)),
499 Arc::new(StringArray::from_iter_values(target_types)),
500 Arc::new(StringArray::from_iter_values(target_keys)),
501 Arc::new(StringArray::from_iter_values(target_jsons)),
502 Arc::new(UInt64Array::from_iter_values(blob_sizes)),
503 Arc::new(StringArray::from_iter(meta_jsons)),
504 Arc::new(UInt64Array::from_iter(node_ids)),
505 ];
506
507 DfRecordBatch::try_new(schema.arrow_schema().clone(), columns)
508 }
509
510 pub fn reserved_table_name_for_inspection() -> &'static str {
512 "__inspect/__mito/__puffin_index_meta"
513 }
514
515 pub fn build_plan(scan_request: ScanRequest) -> Result<LogicalPlan, DataFusionError> {
517 build_plan_helper(
518 scan_request,
519 Self::reserved_table_name_for_inspection(),
520 Self::schema(),
521 )
522 }
523}
524
525fn build_plan_helper(
526 scan_request: ScanRequest,
527 table_name: &str,
528 schema: SchemaRef,
529) -> Result<LogicalPlan, DataFusionError> {
530 let table_source = LogicalTableSource::new(schema.arrow_schema().clone());
531
532 let projection = scan_request.projection;
535 let mut builder = LogicalPlanBuilder::scan(table_name, Arc::new(table_source), None)?;
536
537 for filter in scan_request.filters {
538 builder = builder.filter(filter)?;
539 }
540
541 if let Some(projection) = projection {
542 let projected = schema.arrow_schema().project(&projection)?;
543 builder = builder.project(
544 projected
545 .fields()
546 .iter()
547 .map(|field| datafusion_expr::col(field.name())),
548 )?;
549 }
550
551 if let Some(limit) = scan_request.limit {
552 builder = builder.limit(0, Some(limit))?;
553 }
554
555 builder.build()
556}
557
558#[cfg(test)]
559mod tests {
560 use datafusion_common::TableReference;
561 use datafusion_expr::{Expr, LogicalPlan, Operator, binary_expr, col, lit};
562 use datatypes::arrow::array::{
563 Array, BinaryArray, TimestampMillisecondArray, TimestampNanosecondArray, UInt8Array,
564 UInt32Array, UInt64Array,
565 };
566 use datatypes::arrow_array::StringArray;
567
568 use super::*;
569
570 #[test]
571 fn test_sst_entry_manifest_to_record_batch() {
572 let table_id1: TableId = 1;
574 let region_group1: RegionGroup = 2;
575 let region_seq1: RegionSeq = 3;
576 let region_number1: RegionNumber = ((region_group1 as u32) << 24) | region_seq1;
577 let region_id1 = RegionId::with_group_and_seq(table_id1, region_group1, region_seq1);
578
579 let table_id2: TableId = 5;
580 let region_group2: RegionGroup = 1;
581 let region_seq2: RegionSeq = 42;
582 let region_number2: RegionNumber = ((region_group2 as u32) << 24) | region_seq2;
583 let region_id2 = RegionId::with_group_and_seq(table_id2, region_group2, region_seq2);
584
585 let entries = vec![
586 ManifestSstEntry {
587 table_dir: "tdir1".to_string(),
588 region_id: region_id1,
589 table_id: table_id1,
590 region_number: region_number1,
591 region_group: region_group1,
592 region_sequence: region_seq1,
593 file_id: "f1".to_string(),
594 index_version: 0,
595 level: 1,
596 file_path: "/p1".to_string(),
597 file_size: 100,
598 max_row_group_uncompressed_size: 80,
599 index_file_path: None,
600 index_file_size: None,
601 num_rows: 10,
602 num_row_groups: 2,
603 num_series: Some(5),
604 min_ts: Timestamp::new_millisecond(1000), max_ts: Timestamp::new_second(2), sequence: None,
607 partition_expr: Some("a < 10".to_string()),
608 origin_region_id: region_id1,
609 node_id: Some(1),
610 visible: false,
611 primary_key_min: Some(Bytes::from_static(b"aaa")),
612 primary_key_max: Some(Bytes::from_static(b"zzz")),
613 },
614 ManifestSstEntry {
615 table_dir: "tdir2".to_string(),
616 region_id: region_id2,
617 table_id: table_id2,
618 region_number: region_number2,
619 region_group: region_group2,
620 region_sequence: region_seq2,
621 file_id: "f2".to_string(),
622 index_version: 1,
623 level: 3,
624 file_path: "/p2".to_string(),
625 file_size: 200,
626 max_row_group_uncompressed_size: 160,
627 index_file_path: Some("idx".to_string()),
628 index_file_size: Some(11),
629 num_rows: 20,
630 num_row_groups: 4,
631 num_series: None,
632 min_ts: Timestamp::new_nanosecond(5), max_ts: Timestamp::new_microsecond(2000), sequence: Some(9),
635 partition_expr: None,
636 origin_region_id: region_id2,
637 node_id: None,
638 visible: true,
639 primary_key_min: None,
640 primary_key_max: None,
641 },
642 ];
643
644 let schema = ManifestSstEntry::schema();
645 let batch = ManifestSstEntry::to_record_batch(&entries).unwrap();
646 for projection in [vec![], vec![16, 6, 10], vec![1, 1], (0..26).rev().collect()] {
647 let projected =
648 ManifestSstEntry::to_projected_record_batch(&entries, Some(&projection)).unwrap();
649 assert_eq!(projected, batch.project(&projection).unwrap());
650 assert_eq!(projected.num_rows(), entries.len());
651 }
652 assert!(ManifestSstEntry::to_projected_record_batch(&entries, Some(&[26])).is_err());
653
654 let projected_schema = Arc::new(batch.schema().project(&[6, 9, 6, 11, 25, 9]).unwrap());
655 let builder = ManifestSstBatchBuilder::new(projected_schema.clone());
656 for rows in [entries.as_slice(), &entries[..1], &entries[..0]] {
657 let projected = builder.build(rows).unwrap();
658 assert_eq!(
659 projected,
660 ManifestSstEntry::to_record_batch(rows)
661 .unwrap()
662 .project(&[6, 9, 6, 11, 25, 9])
663 .unwrap()
664 );
665 assert!(Arc::ptr_eq(&projected.schema(), &projected_schema));
666 assert!(Arc::ptr_eq(projected.column(0), projected.column(2)));
667 assert!(Arc::ptr_eq(projected.column(1), projected.column(5)));
668 }
669
670 assert_eq!(schema.arrow_schema().fields().len(), batch.num_columns());
672 assert_eq!(2, batch.num_rows());
673 let expected_columns = [
674 "table_dir",
675 "region_id",
676 "table_id",
677 "region_number",
678 "region_group",
679 "region_sequence",
680 "file_id",
681 "index_version",
682 "level",
683 "file_path",
684 "file_size",
685 "index_file_path",
686 "index_file_size",
687 "num_rows",
688 "num_row_groups",
689 "num_series",
690 "min_ts",
691 "max_ts",
692 "sequence",
693 "origin_region_id",
694 "node_id",
695 "visible",
696 "primary_key_min",
697 "primary_key_max",
698 "max_row_group_uncompressed_size",
699 "partition_expr",
700 ];
701 assert_eq!(
702 expected_columns,
703 schema
704 .arrow_schema()
705 .fields()
706 .iter()
707 .map(|field| field.name().as_str())
708 .collect::<Vec<_>>()
709 .as_slice()
710 );
711 for (i, f) in schema.arrow_schema().fields().iter().enumerate() {
712 assert_eq!(f.name(), batch.schema().field(i).name());
713 assert_eq!(f.is_nullable(), batch.schema().field(i).is_nullable());
714 assert_eq!(f.data_type(), batch.schema().field(i).data_type());
715 }
716
717 let table_dirs = batch
719 .column(0)
720 .as_any()
721 .downcast_ref::<StringArray>()
722 .unwrap();
723 assert_eq!("tdir1", table_dirs.value(0));
724 assert_eq!("tdir2", table_dirs.value(1));
725
726 let region_ids = batch
727 .column(1)
728 .as_any()
729 .downcast_ref::<UInt64Array>()
730 .unwrap();
731 assert_eq!(region_id1.as_u64(), region_ids.value(0));
732 assert_eq!(region_id2.as_u64(), region_ids.value(1));
733
734 let table_ids = batch
735 .column(2)
736 .as_any()
737 .downcast_ref::<UInt32Array>()
738 .unwrap();
739 assert_eq!(table_id1, table_ids.value(0));
740 assert_eq!(table_id2, table_ids.value(1));
741
742 let region_numbers = batch
743 .column(3)
744 .as_any()
745 .downcast_ref::<UInt32Array>()
746 .unwrap();
747 assert_eq!(region_number1, region_numbers.value(0));
748 assert_eq!(region_number2, region_numbers.value(1));
749
750 let region_groups = batch
751 .column(4)
752 .as_any()
753 .downcast_ref::<UInt8Array>()
754 .unwrap();
755 assert_eq!(region_group1, region_groups.value(0));
756 assert_eq!(region_group2, region_groups.value(1));
757
758 let region_sequences = batch
759 .column(5)
760 .as_any()
761 .downcast_ref::<UInt32Array>()
762 .unwrap();
763 assert_eq!(region_seq1, region_sequences.value(0));
764 assert_eq!(region_seq2, region_sequences.value(1));
765
766 let file_ids = batch
767 .column(6)
768 .as_any()
769 .downcast_ref::<StringArray>()
770 .unwrap();
771 assert_eq!("f1", file_ids.value(0));
772 assert_eq!("f2", file_ids.value(1));
773
774 let index_versions = batch
775 .column(7)
776 .as_any()
777 .downcast_ref::<UInt64Array>()
778 .unwrap();
779 assert_eq!(0, index_versions.value(0));
780 assert_eq!(1, index_versions.value(1));
781
782 let levels = batch
783 .column(8)
784 .as_any()
785 .downcast_ref::<UInt8Array>()
786 .unwrap();
787 assert_eq!(1, levels.value(0));
788 assert_eq!(3, levels.value(1));
789
790 let file_paths = batch
791 .column(9)
792 .as_any()
793 .downcast_ref::<StringArray>()
794 .unwrap();
795 assert_eq!("/p1", file_paths.value(0));
796 assert_eq!("/p2", file_paths.value(1));
797
798 let file_sizes = batch
799 .column(10)
800 .as_any()
801 .downcast_ref::<UInt64Array>()
802 .unwrap();
803 assert_eq!(100, file_sizes.value(0));
804 assert_eq!(200, file_sizes.value(1));
805
806 let index_file_paths = batch
807 .column(11)
808 .as_any()
809 .downcast_ref::<StringArray>()
810 .unwrap();
811 assert!(index_file_paths.is_null(0));
812 assert_eq!("idx", index_file_paths.value(1));
813
814 let index_file_sizes = batch
815 .column(12)
816 .as_any()
817 .downcast_ref::<UInt64Array>()
818 .unwrap();
819 assert!(index_file_sizes.is_null(0));
820 assert_eq!(11, index_file_sizes.value(1));
821
822 let num_rows = batch
823 .column(13)
824 .as_any()
825 .downcast_ref::<UInt64Array>()
826 .unwrap();
827 assert_eq!(10, num_rows.value(0));
828 assert_eq!(20, num_rows.value(1));
829
830 let num_row_groups = batch
831 .column(14)
832 .as_any()
833 .downcast_ref::<UInt64Array>()
834 .unwrap();
835 assert_eq!(2, num_row_groups.value(0));
836 assert_eq!(4, num_row_groups.value(1));
837
838 let num_series = batch
839 .column(15)
840 .as_any()
841 .downcast_ref::<UInt64Array>()
842 .unwrap();
843 assert_eq!(5, num_series.value(0));
844 assert!(num_series.is_null(1));
845
846 let min_ts = batch
847 .column(16)
848 .as_any()
849 .downcast_ref::<TimestampNanosecondArray>()
850 .unwrap();
851 assert_eq!(1_000_000_000, min_ts.value(0));
852 assert_eq!(5, min_ts.value(1));
853
854 let max_ts = batch
855 .column(17)
856 .as_any()
857 .downcast_ref::<TimestampNanosecondArray>()
858 .unwrap();
859 assert_eq!(2_000_000_000, max_ts.value(0));
860 assert_eq!(2_000_000, max_ts.value(1));
861
862 let sequences = batch
863 .column(18)
864 .as_any()
865 .downcast_ref::<UInt64Array>()
866 .unwrap();
867 assert!(sequences.is_null(0));
868 assert_eq!(9, sequences.value(1));
869
870 let origin_region_ids = batch
871 .column(19)
872 .as_any()
873 .downcast_ref::<UInt64Array>()
874 .unwrap();
875 assert_eq!(region_id1.as_u64(), origin_region_ids.value(0));
876 assert_eq!(region_id2.as_u64(), origin_region_ids.value(1));
877
878 let node_ids = batch
879 .column(20)
880 .as_any()
881 .downcast_ref::<UInt64Array>()
882 .unwrap();
883 assert_eq!(1, node_ids.value(0));
884 assert!(node_ids.is_null(1));
885
886 let visible = batch
887 .column(21)
888 .as_any()
889 .downcast_ref::<BooleanArray>()
890 .unwrap();
891 assert!(!visible.value(0));
892 assert!(visible.value(1));
893
894 let primary_key_min = batch
895 .column(22)
896 .as_any()
897 .downcast_ref::<BinaryArray>()
898 .unwrap();
899 assert_eq!(b"aaa", primary_key_min.value(0));
900 assert!(primary_key_min.is_null(1));
901
902 let primary_key_max = batch
903 .column(23)
904 .as_any()
905 .downcast_ref::<BinaryArray>()
906 .unwrap();
907 assert_eq!(b"zzz", primary_key_max.value(0));
908 assert!(primary_key_max.is_null(1));
909
910 let max_row_group_uncompressed_sizes = batch
911 .column(24)
912 .as_any()
913 .downcast_ref::<UInt64Array>()
914 .unwrap();
915 assert_eq!(80, max_row_group_uncompressed_sizes.value(0));
916 assert_eq!(160, max_row_group_uncompressed_sizes.value(1));
917
918 let partition_exprs = batch
919 .column(25)
920 .as_any()
921 .downcast_ref::<StringArray>()
922 .unwrap();
923 assert_eq!("a < 10", partition_exprs.value(0));
924 assert!(partition_exprs.is_null(1));
925 }
926
927 #[test]
928 fn test_sst_entry_storage_to_record_batch() {
929 let entries = vec![
930 StorageSstEntry {
931 file_path: "/s1".to_string(),
932 file_size: None,
933 last_modified_ms: None,
934 node_id: Some(1),
935 },
936 StorageSstEntry {
937 file_path: "/s2".to_string(),
938 file_size: Some(123),
939 last_modified_ms: Some(Timestamp::new_millisecond(456)),
940 node_id: None,
941 },
942 ];
943
944 let schema = StorageSstEntry::schema();
945 let batch = StorageSstEntry::to_record_batch(&entries).unwrap();
946
947 assert_eq!(schema.arrow_schema().fields().len(), batch.num_columns());
948 assert_eq!(2, batch.num_rows());
949
950 let file_paths = batch
951 .column(0)
952 .as_any()
953 .downcast_ref::<StringArray>()
954 .unwrap();
955 assert_eq!("/s1", file_paths.value(0));
956 assert_eq!("/s2", file_paths.value(1));
957
958 let file_sizes = batch
959 .column(1)
960 .as_any()
961 .downcast_ref::<UInt64Array>()
962 .unwrap();
963 assert!(file_sizes.is_null(0));
964 assert_eq!(123, file_sizes.value(1));
965
966 let last_modified = batch
967 .column(2)
968 .as_any()
969 .downcast_ref::<TimestampMillisecondArray>()
970 .unwrap();
971 assert!(last_modified.is_null(0));
972 assert_eq!(456, last_modified.value(1));
973
974 let node_ids = batch
975 .column(3)
976 .as_any()
977 .downcast_ref::<UInt64Array>()
978 .unwrap();
979 assert_eq!(1, node_ids.value(0));
980 assert!(node_ids.is_null(1));
981 }
982
983 #[test]
984 fn test_puffin_index_meta_to_record_batch() {
985 let entries = vec![
986 PuffinIndexMetaEntry {
987 table_dir: "table1".to_string(),
988 index_file_path: "index1".to_string(),
989 region_id: RegionId::with_group_and_seq(10, 0, 20),
990 table_id: 10,
991 region_number: 20,
992 region_group: 0,
993 region_sequence: 20,
994 file_id: "file1".to_string(),
995 index_file_size: Some(1024),
996 index_type: "bloom_filter".to_string(),
997 target_type: "column".to_string(),
998 target_key: "1".to_string(),
999 target_json: "{\"column\":1}".to_string(),
1000 blob_size: 256,
1001 meta_json: Some("{\"bloom\":{}}".to_string()),
1002 node_id: Some(42),
1003 },
1004 PuffinIndexMetaEntry {
1005 table_dir: "table2".to_string(),
1006 index_file_path: "index2".to_string(),
1007 region_id: RegionId::with_group_and_seq(11, 0, 21),
1008 table_id: 11,
1009 region_number: 21,
1010 region_group: 0,
1011 region_sequence: 21,
1012 file_id: "file2".to_string(),
1013 index_file_size: None,
1014 index_type: "inverted".to_string(),
1015 target_type: "unknown".to_string(),
1016 target_key: "legacy".to_string(),
1017 target_json: "{}".to_string(),
1018 blob_size: 0,
1019 meta_json: None,
1020 node_id: None,
1021 },
1022 ];
1023
1024 let schema = PuffinIndexMetaEntry::schema();
1025 let batch = PuffinIndexMetaEntry::to_record_batch(&entries).unwrap();
1026
1027 assert_eq!(schema.arrow_schema().fields().len(), batch.num_columns());
1028 assert_eq!(2, batch.num_rows());
1029
1030 let table_dirs = batch
1031 .column(0)
1032 .as_any()
1033 .downcast_ref::<StringArray>()
1034 .unwrap();
1035 assert_eq!("table1", table_dirs.value(0));
1036 assert_eq!("table2", table_dirs.value(1));
1037
1038 let index_file_paths = batch
1039 .column(1)
1040 .as_any()
1041 .downcast_ref::<StringArray>()
1042 .unwrap();
1043 assert_eq!("index1", index_file_paths.value(0));
1044 assert_eq!("index2", index_file_paths.value(1));
1045
1046 let region_ids = batch
1047 .column(2)
1048 .as_any()
1049 .downcast_ref::<UInt64Array>()
1050 .unwrap();
1051 assert_eq!(
1052 RegionId::with_group_and_seq(10, 0, 20).as_u64(),
1053 region_ids.value(0)
1054 );
1055 assert_eq!(
1056 RegionId::with_group_and_seq(11, 0, 21).as_u64(),
1057 region_ids.value(1)
1058 );
1059
1060 let table_ids = batch
1061 .column(3)
1062 .as_any()
1063 .downcast_ref::<UInt32Array>()
1064 .unwrap();
1065 assert_eq!(10, table_ids.value(0));
1066 assert_eq!(11, table_ids.value(1));
1067
1068 let region_numbers = batch
1069 .column(4)
1070 .as_any()
1071 .downcast_ref::<UInt32Array>()
1072 .unwrap();
1073 assert_eq!(20, region_numbers.value(0));
1074 assert_eq!(21, region_numbers.value(1));
1075
1076 let region_groups = batch
1077 .column(5)
1078 .as_any()
1079 .downcast_ref::<UInt8Array>()
1080 .unwrap();
1081 assert_eq!(0, region_groups.value(0));
1082 assert_eq!(0, region_groups.value(1));
1083
1084 let region_sequences = batch
1085 .column(6)
1086 .as_any()
1087 .downcast_ref::<UInt32Array>()
1088 .unwrap();
1089 assert_eq!(20, region_sequences.value(0));
1090 assert_eq!(21, region_sequences.value(1));
1091
1092 let file_ids = batch
1093 .column(7)
1094 .as_any()
1095 .downcast_ref::<StringArray>()
1096 .unwrap();
1097 assert_eq!("file1", file_ids.value(0));
1098 assert_eq!("file2", file_ids.value(1));
1099
1100 let index_file_sizes = batch
1101 .column(8)
1102 .as_any()
1103 .downcast_ref::<UInt64Array>()
1104 .unwrap();
1105 assert_eq!(1024, index_file_sizes.value(0));
1106 assert!(index_file_sizes.is_null(1));
1107
1108 let index_types = batch
1109 .column(9)
1110 .as_any()
1111 .downcast_ref::<StringArray>()
1112 .unwrap();
1113 assert_eq!("bloom_filter", index_types.value(0));
1114 assert_eq!("inverted", index_types.value(1));
1115
1116 let target_types = batch
1117 .column(10)
1118 .as_any()
1119 .downcast_ref::<StringArray>()
1120 .unwrap();
1121 assert_eq!("column", target_types.value(0));
1122 assert_eq!("unknown", target_types.value(1));
1123
1124 let target_keys = batch
1125 .column(11)
1126 .as_any()
1127 .downcast_ref::<StringArray>()
1128 .unwrap();
1129 assert_eq!("1", target_keys.value(0));
1130 assert_eq!("legacy", target_keys.value(1));
1131
1132 let target_json = batch
1133 .column(12)
1134 .as_any()
1135 .downcast_ref::<StringArray>()
1136 .unwrap();
1137 assert_eq!("{\"column\":1}", target_json.value(0));
1138 assert_eq!("{}", target_json.value(1));
1139
1140 let blob_sizes = batch
1141 .column(13)
1142 .as_any()
1143 .downcast_ref::<UInt64Array>()
1144 .unwrap();
1145 assert_eq!(256, blob_sizes.value(0));
1146 assert_eq!(0, blob_sizes.value(1));
1147
1148 let meta_jsons = batch
1149 .column(14)
1150 .as_any()
1151 .downcast_ref::<StringArray>()
1152 .unwrap();
1153 assert_eq!("{\"bloom\":{}}", meta_jsons.value(0));
1154 assert!(meta_jsons.is_null(1));
1155
1156 let node_ids = batch
1157 .column(15)
1158 .as_any()
1159 .downcast_ref::<UInt64Array>()
1160 .unwrap();
1161 assert_eq!(42, node_ids.value(0));
1162 assert!(node_ids.is_null(1));
1163 }
1164
1165 #[test]
1166 fn test_manifest_build_plan() {
1167 let request = ScanRequest {
1168 projection: Some(vec![0, 1]),
1169 filters: vec![col("table_id").gt(lit(0u32))],
1170 limit: Some(5),
1171 ..Default::default()
1172 };
1173
1174 let plan = ManifestSstEntry::build_plan(request).unwrap();
1175
1176 let LogicalPlan::Limit(limit) = &plan else {
1178 panic!("expected limit")
1179 };
1180 let LogicalPlan::Projection(projection) = limit.input.as_ref() else {
1181 panic!("expected projection")
1182 };
1183 let LogicalPlan::Filter(filter) = projection.input.as_ref() else {
1184 panic!("expected filter")
1185 };
1186 let LogicalPlan::TableScan(scan) = filter.input.as_ref() else {
1187 panic!("expected table scan")
1188 };
1189 assert_eq!(
1190 scan.table_name,
1191 TableReference::bare(ManifestSstEntry::reserved_table_name_for_inspection())
1192 );
1193 assert_eq!(scan.projection, None);
1194 assert_eq!(
1195 filter.predicate,
1196 Expr::Column(datafusion_common::Column::new(
1197 Some(scan.table_name.clone()),
1198 "table_id",
1199 ))
1200 .gt(lit(0u32))
1201 );
1202
1203 let fields = plan.schema().fields();
1204 assert_eq!(fields.len(), 2);
1205 assert_eq!(fields[0].name(), "table_dir");
1206 assert_eq!(fields[1].name(), "region_id");
1207 }
1208
1209 #[test]
1210 fn test_storage_build_plan() {
1211 let projection = Some(vec![0, 2]);
1212 let request = ScanRequest {
1213 projection,
1214 filters: vec![binary_expr(col("file_path"), Operator::Eq, lit("/a"))],
1215 limit: Some(1),
1216 ..Default::default()
1217 };
1218
1219 let plan = StorageSstEntry::build_plan(request).unwrap();
1220 let (scan, has_filter, has_limit) = extract_scan(&plan);
1221 assert!(has_filter);
1222 assert!(has_limit);
1223 assert_eq!(
1224 scan.table_name,
1225 TableReference::bare(StorageSstEntry::reserved_table_name_for_inspection())
1226 );
1227 assert_eq!(scan.projection, None);
1228
1229 let fields = plan.schema().fields();
1230 assert_eq!(fields.len(), 2);
1231 assert_eq!(fields[0].name(), "file_path");
1232 assert_eq!(fields[1].name(), "last_modified_ms");
1233 }
1234
1235 fn extract_scan(plan: &LogicalPlan) -> (&datafusion_expr::logical_plan::TableScan, bool, bool) {
1237 use datafusion_expr::logical_plan::Limit;
1238
1239 match plan {
1240 LogicalPlan::Filter(f) => {
1241 let (scan, _, has_limit) = extract_scan(&f.input);
1242 (scan, true, has_limit)
1243 }
1244 LogicalPlan::Limit(Limit { input, .. }) => {
1245 let (scan, has_filter, _) = extract_scan(input);
1246 (scan, has_filter, true)
1247 }
1248 LogicalPlan::Projection(projection) => extract_scan(&projection.input),
1249 LogicalPlan::TableScan(scan) => (scan, false, false),
1250 other => panic!("unexpected plan: {other:?}"),
1251 }
1252 }
1253}