1use std::io::Write;
2
3use colored::Colorize;
4use serde::Serialize;
5
6use crate::cli::{wprint, wprintln};
7use crate::innodb::checksum;
8use crate::innodb::compression;
9use crate::innodb::encryption;
10use crate::innodb::health::compute_fill_factor;
11use crate::innodb::index::{FsegHeader, IndexHeader, SystemRecords};
12use crate::innodb::lob::{BlobPageHeader, LobFirstPageHeader};
13use crate::innodb::page::{FilHeader, FspHeader};
14use crate::innodb::page_types::PageType;
15use crate::innodb::record::walk_compact_records;
16use crate::innodb::tablespace::Tablespace;
17use crate::innodb::undo::{UndoPageHeader, UndoSegmentHeader};
18use crate::util::hex::format_offset;
19use crate::IdbError;
20
21pub struct PagesOptions {
23 pub file: String,
25 pub page: Option<u64>,
27 pub verbose: bool,
29 pub show_empty: bool,
31 pub list_mode: bool,
33 pub filter_type: Option<String>,
35 pub page_size: Option<u32>,
37 pub json: bool,
39 pub keyring: Option<String>,
41 pub mmap: bool,
43 pub deleted: bool,
45 pub csv: bool,
47}
48
49#[derive(Serialize)]
51struct PageDetailJson {
52 page_number: u64,
53 header: FilHeader,
54 page_type_name: String,
55 page_type_description: String,
56 byte_start: u64,
57 byte_end: u64,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 index_header: Option<IndexHeader>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 fsp_header: Option<FspHeader>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 fill_factor: Option<f64>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 delete_marked_count: Option<usize>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 total_record_count: Option<usize>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 delete_marked_pct: Option<f64>,
70}
71
72pub fn execute(opts: &PagesOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
96 let mut ts = crate::cli::open_tablespace(&opts.file, opts.page_size, opts.mmap)?;
97
98 if let Some(ref keyring_path) = opts.keyring {
99 crate::cli::setup_decryption(&mut ts, keyring_path)?;
100 }
101
102 let page_size = ts.page_size();
103
104 if opts.json {
105 return execute_json(opts, &mut ts, page_size, writer);
106 }
107
108 if opts.csv {
109 return execute_csv(opts, &mut ts, page_size, writer);
110 }
111
112 if let Some(page_num) = opts.page {
113 let page_data = ts.read_page(page_num)?;
114 print_full_page(
115 &page_data,
116 page_num,
117 page_size,
118 opts.verbose,
119 opts.deleted,
120 writer,
121 )?;
122 return Ok(());
123 }
124
125 if opts.filter_type.is_none() {
127 let page0 = ts.read_page(0)?;
128 if let Some(fsp) = FspHeader::parse(&page0) {
129 print_fsp_header_detail(&fsp, &page0, opts.verbose, ts.vendor_info(), writer)?;
130 }
131 }
132
133 for page_num in 0..ts.page_count() {
134 let page_data = ts.read_page(page_num)?;
135 let header = match FilHeader::parse(&page_data) {
136 Some(h) => h,
137 None => continue,
138 };
139
140 if !opts.show_empty && header.checksum == 0 && header.page_type == PageType::Allocated {
142 continue;
143 }
144
145 if let Some(ref filter) = opts.filter_type {
147 if !matches_page_type_filter(&header.page_type, filter) {
148 continue;
149 }
150 }
151
152 if opts.list_mode {
153 print_list_line(&page_data, page_num, page_size, opts.deleted, writer)?;
154 } else {
155 print_full_page(
156 &page_data,
157 page_num,
158 page_size,
159 opts.verbose,
160 opts.deleted,
161 writer,
162 )?;
163 }
164 }
165
166 Ok(())
167}
168
169fn execute_csv(
171 opts: &PagesOptions,
172 ts: &mut Tablespace,
173 page_size: u32,
174 writer: &mut dyn Write,
175) -> Result<(), IdbError> {
176 wprintln!(
177 writer,
178 "page_number,page_type,byte_start,index_id,fill_factor"
179 )?;
180
181 let range: Box<dyn Iterator<Item = u64>> = if let Some(p) = opts.page {
182 Box::new(std::iter::once(p))
183 } else {
184 Box::new(0..ts.page_count())
185 };
186
187 for page_num in range {
188 let page_data = ts.read_page(page_num)?;
189 let header = match FilHeader::parse(&page_data) {
190 Some(h) => h,
191 None => continue,
192 };
193
194 if !opts.show_empty && header.checksum == 0 && header.page_type == PageType::Allocated {
195 continue;
196 }
197
198 if let Some(ref filter) = opts.filter_type {
199 if !matches_page_type_filter(&header.page_type, filter) {
200 continue;
201 }
202 }
203
204 let pt = header.page_type;
205 let byte_start = page_num * page_size as u64;
206
207 let (index_id, fill_factor) = if pt == PageType::Index {
208 let idx = IndexHeader::parse(&page_data);
209 match idx {
210 Some(i) => {
211 let ff = compute_fill_factor(i.heap_top, i.garbage, page_size);
212 (Some(i.index_id), Some(format!("{:.4}", ff)))
213 }
214 None => (None, None),
215 }
216 } else {
217 (None, None)
218 };
219
220 wprintln!(
221 writer,
222 "{},{},{},{},{}",
223 page_num,
224 crate::cli::csv_escape(pt.name()),
225 byte_start,
226 index_id.map(|id| id.to_string()).unwrap_or_default(),
227 fill_factor.unwrap_or_default()
228 )?;
229 }
230 Ok(())
231}
232
233fn execute_json(
235 opts: &PagesOptions,
236 ts: &mut Tablespace,
237 page_size: u32,
238 writer: &mut dyn Write,
239) -> Result<(), IdbError> {
240 let mut pages = Vec::new();
241
242 let range: Box<dyn Iterator<Item = u64>> = if let Some(p) = opts.page {
243 Box::new(std::iter::once(p))
244 } else {
245 Box::new(0..ts.page_count())
246 };
247
248 for page_num in range {
249 let page_data = ts.read_page(page_num)?;
250 let header = match FilHeader::parse(&page_data) {
251 Some(h) => h,
252 None => continue,
253 };
254
255 if !opts.show_empty && header.checksum == 0 && header.page_type == PageType::Allocated {
256 continue;
257 }
258
259 if let Some(ref filter) = opts.filter_type {
260 if !matches_page_type_filter(&header.page_type, filter) {
261 continue;
262 }
263 }
264
265 let pt = header.page_type;
266 let byte_start = page_num * page_size as u64;
267
268 let index_header = if pt == PageType::Index {
269 IndexHeader::parse(&page_data)
270 } else {
271 None
272 };
273
274 let fill_factor = index_header.as_ref().map(|idx| {
275 (compute_fill_factor(idx.heap_top, idx.garbage, page_size) * 10000.0).round() / 10000.0
276 });
277
278 let (delete_marked_count, total_record_count, delete_marked_pct) =
279 if pt == PageType::Index && opts.deleted {
280 let recs = walk_compact_records(&page_data);
281 let total = recs.len();
282 let deleted = recs.iter().filter(|r| r.header.delete_mark()).count();
283 let pct = if total > 0 {
284 (deleted as f64 / total as f64 * 10000.0).round() / 100.0
285 } else {
286 0.0
287 };
288 (Some(deleted), Some(total), Some(pct))
289 } else {
290 (None, None, None)
291 };
292
293 let fsp_header = if page_num == 0 {
294 FspHeader::parse(&page_data)
295 } else {
296 None
297 };
298
299 pages.push(PageDetailJson {
300 page_number: page_num,
301 page_type_name: pt.name().to_string(),
302 page_type_description: pt.description().to_string(),
303 byte_start,
304 byte_end: byte_start + page_size as u64,
305 header,
306 index_header,
307 fsp_header,
308 fill_factor,
309 delete_marked_count,
310 total_record_count,
311 delete_marked_pct,
312 });
313 }
314
315 let json = serde_json::to_string_pretty(&pages)
316 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
317 wprintln!(writer, "{}", json)?;
318 Ok(())
319}
320
321fn print_list_line(
323 page_data: &[u8],
324 page_num: u64,
325 page_size: u32,
326 show_deleted: bool,
327 writer: &mut dyn Write,
328) -> Result<(), IdbError> {
329 let header = match FilHeader::parse(page_data) {
330 Some(h) => h,
331 None => return Ok(()),
332 };
333
334 let pt = header.page_type;
335 let byte_start = page_num * page_size as u64;
336
337 wprint!(
338 writer,
339 "-- Page {} - {}: {}",
340 page_num,
341 pt.name(),
342 pt.description()
343 )?;
344
345 if pt == PageType::Index {
346 if let Some(idx) = IndexHeader::parse(page_data) {
347 wprint!(writer, ", Index ID: {}", idx.index_id)?;
348
349 let ff = compute_fill_factor(idx.heap_top, idx.garbage, page_size);
350 let pct = ff * 100.0;
351 let fill_str = if pct >= 80.0 {
352 format!("{:.1}%", pct).green().to_string()
353 } else if pct >= 50.0 {
354 format!("{:.1}%", pct).yellow().to_string()
355 } else {
356 format!("{:.1}%", pct).red().to_string()
357 };
358 wprint!(writer, ", Fill: {}", fill_str)?;
359
360 if show_deleted {
361 let recs = walk_compact_records(page_data);
362 let total = recs.len();
363 let deleted = recs.iter().filter(|r| r.header.delete_mark()).count();
364 if total > 0 {
365 let del_pct = deleted as f64 / total as f64 * 100.0;
366 wprint!(writer, " (del: {}/{}, {:.1}%)", deleted, total, del_pct)?;
367 }
368 }
369 }
370 }
371
372 wprintln!(writer, ", Byte Start: {}", format_offset(byte_start))?;
373 Ok(())
374}
375
376fn print_full_page(
378 page_data: &[u8],
379 page_num: u64,
380 page_size: u32,
381 verbose: bool,
382 show_deleted: bool,
383 writer: &mut dyn Write,
384) -> Result<(), IdbError> {
385 let header = match FilHeader::parse(page_data) {
386 Some(h) => h,
387 None => {
388 eprintln!("Could not parse FIL header for page {}", page_num);
389 return Ok(());
390 }
391 };
392
393 let byte_start = page_num * page_size as u64;
394 let byte_end = byte_start + page_size as u64;
395 let pt = header.page_type;
396
397 wprintln!(writer)?;
399 wprintln!(writer, "=== HEADER: Page {}", header.page_number)?;
400 wprintln!(writer, "Byte Start: {}", format_offset(byte_start))?;
401 wprintln!(
402 writer,
403 "Page Type: {}\n-- {}: {} - {}",
404 pt.as_u16(),
405 pt.name(),
406 pt.description(),
407 pt.usage()
408 )?;
409
410 wprint!(writer, "Prev Page: ")?;
411 if !header.has_prev() {
412 wprintln!(writer, "Not used.")?;
413 } else {
414 wprintln!(writer, "{}", header.prev_page)?;
415 }
416
417 wprint!(writer, "Next Page: ")?;
418 if !header.has_next() {
419 wprintln!(writer, "Not used.")?;
420 } else {
421 wprintln!(writer, "{}", header.next_page)?;
422 }
423
424 wprintln!(writer, "LSN: {}", header.lsn)?;
425 wprintln!(writer, "Space ID: {}", header.space_id)?;
426 wprintln!(writer, "Checksum: {}", header.checksum)?;
427
428 if pt == PageType::Index {
430 if let Some(idx) = IndexHeader::parse(page_data) {
431 wprintln!(writer)?;
432 print_index_header(&idx, header.page_number, verbose, writer)?;
433
434 let ff = compute_fill_factor(idx.heap_top, idx.garbage, page_size);
436 let pct = ff * 100.0;
437 let fill_str = if pct >= 80.0 {
438 format!("{:.1}%", pct).green().to_string()
439 } else if pct >= 50.0 {
440 format!("{:.1}%", pct).yellow().to_string()
441 } else {
442 format!("{:.1}%", pct).red().to_string()
443 };
444 wprintln!(writer, "Fill Factor: {}", fill_str)?;
445
446 if show_deleted {
448 let recs = walk_compact_records(page_data);
449 let total = recs.len();
450 let deleted = recs.iter().filter(|r| r.header.delete_mark()).count();
451 wprintln!(writer)?;
452 wprintln!(
453 writer,
454 "=== Delete-Marked Records: Page {}",
455 header.page_number
456 )?;
457 wprintln!(writer, "Total Records: {}", total)?;
458 wprintln!(writer, "Delete-Marked: {}", deleted)?;
459 if total > 0 {
460 let del_pct = deleted as f64 / total as f64 * 100.0;
461 wprintln!(writer, "Delete-Marked Ratio: {:.1}%", del_pct)?;
462 }
463 }
464
465 wprintln!(writer)?;
466 print_fseg_headers(page_data, header.page_number, &idx, verbose, writer)?;
467
468 wprintln!(writer)?;
469 print_system_records(page_data, header.page_number, writer)?;
470 }
471 }
472
473 if matches!(pt, PageType::Blob | PageType::ZBlob | PageType::ZBlob2) {
475 if let Some(blob_hdr) = BlobPageHeader::parse(page_data) {
476 wprintln!(writer)?;
477 wprintln!(writer, "=== BLOB Header: Page {}", header.page_number)?;
478 wprintln!(writer, "Data Length: {} bytes", blob_hdr.part_len)?;
479 if blob_hdr.has_next() {
480 wprintln!(writer, "Next BLOB Page: {}", blob_hdr.next_page_no)?;
481 } else {
482 wprintln!(writer, "Next BLOB Page: None (last in chain)")?;
483 }
484 }
485 }
486
487 if pt == PageType::LobFirst {
489 if let Some(lob_hdr) = LobFirstPageHeader::parse(page_data) {
490 wprintln!(writer)?;
491 wprintln!(
492 writer,
493 "=== LOB First Page Header: Page {}",
494 header.page_number
495 )?;
496 wprintln!(writer, "Version: {}", lob_hdr.version)?;
497 wprintln!(writer, "Flags: {}", lob_hdr.flags)?;
498 wprintln!(writer, "Total Data Length: {} bytes", lob_hdr.data_len)?;
499 if lob_hdr.trx_id > 0 {
500 wprintln!(writer, "Transaction ID: {}", lob_hdr.trx_id)?;
501 }
502 }
503 }
504
505 if pt == PageType::UndoLog {
507 if let Some(undo_hdr) = UndoPageHeader::parse(page_data) {
508 wprintln!(writer)?;
509 wprintln!(writer, "=== UNDO Header: Page {}", header.page_number)?;
510 wprintln!(
511 writer,
512 "Undo Type: {} ({})",
513 undo_hdr.page_type.name(),
514 undo_hdr.page_type.name()
515 )?;
516 wprintln!(writer, "Log Start Offset: {}", undo_hdr.start)?;
517 wprintln!(writer, "Free Offset: {}", undo_hdr.free)?;
518 wprintln!(
519 writer,
520 "Used Bytes: {}",
521 undo_hdr.free.saturating_sub(undo_hdr.start)
522 )?;
523
524 if let Some(seg_hdr) = UndoSegmentHeader::parse(page_data) {
525 wprintln!(writer, "Segment State: {}", seg_hdr.state.name())?;
526 wprintln!(writer, "Last Log Offset: {}", seg_hdr.last_log)?;
527 }
528 }
529 }
530
531 wprintln!(writer)?;
533 let ps = page_size as usize;
534 if page_data.len() >= ps {
535 let trailer_offset = ps - 8;
536 if let Some(trailer) = crate::innodb::page::FilTrailer::parse(&page_data[trailer_offset..])
537 {
538 wprintln!(writer, "=== TRAILER: Page {}", header.page_number)?;
539 wprintln!(writer, "Old-style Checksum: {}", trailer.checksum)?;
540 wprintln!(writer, "Low 32 bits of LSN: {}", trailer.lsn_low32)?;
541 wprintln!(writer, "Byte End: {}", format_offset(byte_end))?;
542
543 if verbose {
544 let csum_result = checksum::validate_checksum(page_data, page_size, None);
545 let status = if csum_result.valid {
546 "OK".green().to_string()
547 } else {
548 "MISMATCH".red().to_string()
549 };
550 wprintln!(
551 writer,
552 "Checksum Status: {} ({:?})",
553 status,
554 csum_result.algorithm
555 )?;
556
557 let lsn_valid = checksum::validate_lsn(page_data, page_size);
558 let lsn_status = if lsn_valid {
559 "OK".green().to_string()
560 } else {
561 "MISMATCH".red().to_string()
562 };
563 wprintln!(writer, "LSN Consistency: {}", lsn_status)?;
564 }
565 }
566 }
567
568 Ok(())
569}
570
571fn print_index_header(
573 idx: &IndexHeader,
574 page_num: u32,
575 verbose: bool,
576 writer: &mut dyn Write,
577) -> Result<(), IdbError> {
578 wprintln!(writer, "=== INDEX Header: Page {}", page_num)?;
579 wprintln!(writer, "Index ID: {}", idx.index_id)?;
580 wprintln!(writer, "Node Level: {}", idx.level)?;
581
582 if idx.max_trx_id > 0 {
583 wprintln!(writer, "Max Transaction ID: {}", idx.max_trx_id)?;
584 } else {
585 wprintln!(writer, "-- Secondary Index")?;
586 }
587
588 wprintln!(writer, "Directory Slots: {}", idx.n_dir_slots)?;
589 if verbose {
590 wprintln!(writer, "-- Number of slots in page directory")?;
591 }
592
593 wprintln!(writer, "Heap Top: {}", idx.heap_top)?;
594 if verbose {
595 wprintln!(writer, "-- Pointer to record heap top")?;
596 }
597
598 wprintln!(writer, "Records in Page: {}", idx.n_recs)?;
599 wprintln!(
600 writer,
601 "Records in Heap: {} (compact: {})",
602 idx.n_heap(),
603 idx.is_compact()
604 )?;
605 if verbose {
606 wprintln!(writer, "-- Number of records in heap")?;
607 }
608
609 wprintln!(writer, "Start of Free Record List: {}", idx.free)?;
610 wprintln!(writer, "Garbage Bytes: {}", idx.garbage)?;
611 if verbose {
612 wprintln!(writer, "-- Number of bytes in deleted records.")?;
613 }
614
615 wprintln!(writer, "Last Insert: {}", idx.last_insert)?;
616 wprintln!(
617 writer,
618 "Last Insert Direction: {} - {}",
619 idx.direction,
620 idx.direction_name()
621 )?;
622 wprintln!(writer, "Inserts in this direction: {}", idx.n_direction)?;
623 if verbose {
624 wprintln!(
625 writer,
626 "-- Number of consecutive inserts in this direction."
627 )?;
628 }
629
630 Ok(())
631}
632
633fn print_fseg_headers(
635 page_data: &[u8],
636 page_num: u32,
637 idx: &IndexHeader,
638 verbose: bool,
639 writer: &mut dyn Write,
640) -> Result<(), IdbError> {
641 wprintln!(
642 writer,
643 "=== FSEG_HDR - File Segment Header: Page {}",
644 page_num
645 )?;
646
647 if let Some(leaf) = FsegHeader::parse_leaf(page_data) {
648 wprintln!(writer, "Inode Space ID: {}", leaf.space_id)?;
649 wprintln!(writer, "Inode Page Number: {}", leaf.page_no)?;
650 wprintln!(writer, "Inode Offset: {}", leaf.offset)?;
651 }
652
653 if idx.is_leaf() {
654 if let Some(internal) = FsegHeader::parse_internal(page_data) {
655 wprintln!(writer, "Non-leaf Space ID: {}", internal.space_id)?;
656 if verbose {
657 wprintln!(writer, "Non-leaf Page Number: {}", internal.page_no)?;
658 wprintln!(writer, "Non-leaf Offset: {}", internal.offset)?;
659 }
660 }
661 }
662
663 Ok(())
664}
665
666fn print_system_records(
668 page_data: &[u8],
669 page_num: u32,
670 writer: &mut dyn Write,
671) -> Result<(), IdbError> {
672 let sys = match SystemRecords::parse(page_data) {
673 Some(s) => s,
674 None => return Ok(()),
675 };
676
677 wprintln!(writer, "=== INDEX System Records: Page {}", page_num)?;
678 wprintln!(
679 writer,
680 "Index Record Status: {} - (Decimal: {}) {}",
681 sys.rec_status,
682 sys.rec_status,
683 sys.rec_status_name()
684 )?;
685 wprintln!(writer, "Number of records owned: {}", sys.n_owned)?;
686 wprintln!(writer, "Deleted: {}", if sys.deleted { "1" } else { "0" })?;
687 wprintln!(writer, "Heap Number: {}", sys.heap_no)?;
688 wprintln!(writer, "Next Record Offset (Infimum): {}", sys.infimum_next)?;
689 wprintln!(
690 writer,
691 "Next Record Offset (Supremum): {}",
692 sys.supremum_next
693 )?;
694 wprintln!(
695 writer,
696 "Left-most node on non-leaf level: {}",
697 if sys.min_rec { "1" } else { "0" }
698 )?;
699
700 Ok(())
701}
702
703fn print_fsp_header_detail(
705 fsp: &FspHeader,
706 page0: &[u8],
707 verbose: bool,
708 vendor_info: &crate::innodb::vendor::VendorInfo,
709 writer: &mut dyn Write,
710) -> Result<(), IdbError> {
711 wprintln!(writer, "=== File Header")?;
712 wprintln!(writer, "Vendor: {}", vendor_info)?;
713 wprintln!(writer, "Space ID: {}", fsp.space_id)?;
714 if verbose {
715 wprintln!(writer, "-- Offset 38, Length 4")?;
716 }
717 wprintln!(writer, "Size: {}", fsp.size)?;
718 wprintln!(writer, "Flags: {}", fsp.flags)?;
719 wprintln!(
720 writer,
721 "Page Free Limit: {} (this should always be 64 on a single-table file)",
722 fsp.free_limit
723 )?;
724
725 let comp = compression::detect_compression(fsp.flags, Some(vendor_info));
727 let enc = encryption::detect_encryption(fsp.flags, Some(vendor_info));
728 if comp != compression::CompressionAlgorithm::None {
729 wprintln!(writer, "Compression: {}", comp)?;
730 }
731 if enc != encryption::EncryptionAlgorithm::None {
732 wprintln!(writer, "Encryption: {}", enc)?;
733
734 if let Some(info) = encryption::parse_encryption_info(
736 page0,
737 fsp.page_size_from_flags_with_vendor(vendor_info),
738 ) {
739 let version_desc = match info.magic_version {
740 1 => "V1",
741 2 => "V2",
742 3 => "V3 (MySQL 8.0.5+)",
743 _ => "Unknown",
744 };
745 wprintln!(writer, " Master Key ID: {}", info.master_key_id)?;
746 wprintln!(writer, " Server UUID: {}", info.server_uuid)?;
747 wprintln!(writer, " Magic: {}", version_desc)?;
748 }
749 }
750
751 let seg_id_offset = crate::innodb::constants::FIL_PAGE_DATA + 72;
753 if page0.len() >= seg_id_offset + 8 {
754 use byteorder::ByteOrder;
755 let seg_id = byteorder::BigEndian::read_u64(&page0[seg_id_offset..]);
756 wprintln!(writer, "First Unused Segment ID: {}", seg_id)?;
757 }
758
759 Ok(())
760}
761
762fn matches_page_type_filter(page_type: &PageType, filter: &str) -> bool {
767 let filter_upper = filter.to_uppercase();
768 let type_name = page_type.name();
769
770 if type_name == filter_upper {
772 return true;
773 }
774
775 match filter_upper.as_str() {
777 "UNDO" => *page_type == PageType::UndoLog,
778 "BLOB" => matches!(
779 page_type,
780 PageType::Blob | PageType::ZBlob | PageType::ZBlob2
781 ),
782 "LOB" => matches!(
783 page_type,
784 PageType::LobIndex
785 | PageType::LobData
786 | PageType::LobFirst
787 | PageType::ZlobFirst
788 | PageType::ZlobData
789 | PageType::ZlobIndex
790 | PageType::ZlobFrag
791 | PageType::ZlobFragEntry
792 ),
793 "ZLOB" => matches!(
794 page_type,
795 PageType::ZlobFirst
796 | PageType::ZlobData
797 | PageType::ZlobIndex
798 | PageType::ZlobFrag
799 | PageType::ZlobFragEntry
800 ),
801 "SDI" => matches!(
802 page_type,
803 PageType::Sdi | PageType::SdiBlob | PageType::SdiZblob
804 ),
805 "COMPRESSED" | "COMP" => matches!(
806 page_type,
807 PageType::Compressed
808 | PageType::CompressedEncrypted
809 | PageType::PageCompressed
810 | PageType::PageCompressedEncrypted
811 ),
812 "ENCRYPTED" | "ENC" => matches!(
813 page_type,
814 PageType::Encrypted
815 | PageType::CompressedEncrypted
816 | PageType::EncryptedRtree
817 | PageType::PageCompressedEncrypted
818 ),
819 "INSTANT" => *page_type == PageType::Instant,
820 _ => type_name.contains(&filter_upper),
821 }
822}