1use std::collections::BTreeMap;
2use std::io::Write;
3use std::path::Path;
4
5use byteorder::{BigEndian, ByteOrder};
6use serde::Serialize;
7
8use crate::cli::wprintln;
9use crate::innodb::constants::FIL_PAGE_DATA;
10use crate::util::fs::find_tablespace_files;
11use crate::IdbError;
12
13pub struct TsidOptions {
15 pub datadir: String,
17 pub list: bool,
19 pub tablespace_id: Option<u32>,
21 pub json: bool,
23 pub page_size: Option<u32>,
25 pub mmap: bool,
27 pub depth: Option<u32>,
29}
30
31#[derive(Serialize)]
32struct TsidResultJson {
33 datadir: String,
34 tablespaces: Vec<TsidEntryJson>,
35}
36
37#[derive(Serialize)]
38struct TsidEntryJson {
39 file: String,
40 space_id: u32,
41}
42
43pub fn execute(opts: &TsidOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
62 let datadir = Path::new(&opts.datadir);
63 if !datadir.is_dir() {
64 return Err(IdbError::Argument(format!(
65 "Data directory does not exist: {}",
66 opts.datadir
67 )));
68 }
69
70 let ibd_files = find_tablespace_files(datadir, &["ibd", "ibu"], opts.depth)?;
71
72 if ibd_files.is_empty() {
73 if opts.json {
74 let result = TsidResultJson {
75 datadir: opts.datadir.clone(),
76 tablespaces: Vec::new(),
77 };
78 let json = serde_json::to_string_pretty(&result)
79 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
80 wprintln!(writer, "{}", json)?;
81 } else {
82 wprintln!(writer, "No .ibd/.ibu files found in {}", opts.datadir)?;
83 }
84 return Ok(());
85 }
86
87 let mut results: BTreeMap<String, u32> = BTreeMap::new();
89
90 for ibd_path in &ibd_files {
91 let path_str = ibd_path.to_string_lossy();
92 let mut ts = match crate::cli::open_tablespace(&path_str, opts.page_size, opts.mmap) {
93 Ok(t) => t,
94 Err(_) => continue,
95 };
96
97 let space_id = match ts.fsp_header() {
98 Some(fsp) => fsp.space_id,
99 None => {
100 match ts.read_page(0) {
102 Ok(page0) => {
103 if page0.len() >= FIL_PAGE_DATA + 4 {
104 BigEndian::read_u32(&page0[FIL_PAGE_DATA..])
105 } else {
106 continue;
107 }
108 }
109 Err(_) => continue,
110 }
111 }
112 };
113
114 let display_path = ibd_path
115 .strip_prefix(datadir)
116 .unwrap_or(ibd_path)
117 .to_string_lossy()
118 .to_string();
119
120 if let Some(target_id) = opts.tablespace_id {
122 if space_id != target_id {
123 continue;
124 }
125 }
126
127 results.insert(display_path, space_id);
128 }
129
130 if opts.json {
131 let tablespaces: Vec<TsidEntryJson> = results
132 .iter()
133 .map(|(path, &space_id)| TsidEntryJson {
134 file: path.clone(),
135 space_id,
136 })
137 .collect();
138
139 let result = TsidResultJson {
140 datadir: opts.datadir.clone(),
141 tablespaces,
142 };
143
144 let json = serde_json::to_string_pretty(&result)
145 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
146 wprintln!(writer, "{}", json)?;
147 } else {
148 for (path, space_id) in &results {
150 wprintln!(writer, "{} - Space ID: {}", path, space_id)?;
151 }
152
153 if results.is_empty() {
154 if let Some(target_id) = opts.tablespace_id {
155 wprintln!(writer, "Tablespace ID {} not found.", target_id)?;
156 }
157 }
158 }
159
160 Ok(())
161}