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::innodb::tablespace::Tablespace;
11use crate::IdbError;
12
13pub struct TsidOptions {
14 pub datadir: String,
15 pub list: bool,
16 pub tablespace_id: Option<u32>,
17 pub json: bool,
18 pub page_size: Option<u32>,
19}
20
21#[derive(Serialize)]
22struct TsidResultJson {
23 datadir: String,
24 tablespaces: Vec<TsidEntryJson>,
25}
26
27#[derive(Serialize)]
28struct TsidEntryJson {
29 file: String,
30 space_id: u32,
31}
32
33pub fn execute(opts: &TsidOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
34 let datadir = Path::new(&opts.datadir);
35 if !datadir.is_dir() {
36 return Err(IdbError::Argument(format!(
37 "Data directory does not exist: {}",
38 opts.datadir
39 )));
40 }
41
42 let ibd_files = find_ibd_and_ibu_files(datadir)?;
43
44 if ibd_files.is_empty() {
45 if opts.json {
46 let result = TsidResultJson {
47 datadir: opts.datadir.clone(),
48 tablespaces: Vec::new(),
49 };
50 let json = serde_json::to_string_pretty(&result)
51 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
52 wprintln!(writer, "{}", json)?;
53 } else {
54 wprintln!(writer, "No .ibd/.ibu files found in {}", opts.datadir)?;
55 }
56 return Ok(());
57 }
58
59 let mut results: BTreeMap<String, u32> = BTreeMap::new();
61
62 for ibd_path in &ibd_files {
63 let mut ts = match match opts.page_size {
64 Some(ps) => Tablespace::open_with_page_size(ibd_path, ps),
65 None => Tablespace::open(ibd_path),
66 } {
67 Ok(t) => t,
68 Err(_) => continue,
69 };
70
71 let space_id = match ts.fsp_header() {
72 Some(fsp) => fsp.space_id,
73 None => {
74 match ts.read_page(0) {
76 Ok(page0) => {
77 if page0.len() >= FIL_PAGE_DATA + 4 {
78 BigEndian::read_u32(&page0[FIL_PAGE_DATA..])
79 } else {
80 continue;
81 }
82 }
83 Err(_) => continue,
84 }
85 }
86 };
87
88 let display_path = ibd_path
89 .strip_prefix(datadir)
90 .unwrap_or(ibd_path)
91 .to_string_lossy()
92 .to_string();
93
94 if let Some(target_id) = opts.tablespace_id {
96 if space_id != target_id {
97 continue;
98 }
99 }
100
101 results.insert(display_path, space_id);
102 }
103
104 if opts.json {
105 let tablespaces: Vec<TsidEntryJson> = results
106 .iter()
107 .map(|(path, &space_id)| TsidEntryJson {
108 file: path.clone(),
109 space_id,
110 })
111 .collect();
112
113 let result = TsidResultJson {
114 datadir: opts.datadir.clone(),
115 tablespaces,
116 };
117
118 let json = serde_json::to_string_pretty(&result)
119 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
120 wprintln!(writer, "{}", json)?;
121 } else {
122 for (path, space_id) in &results {
124 wprintln!(writer, "{} - Space ID: {}", path, space_id)?;
125 }
126
127 if results.is_empty() {
128 if let Some(target_id) = opts.tablespace_id {
129 wprintln!(writer, "Tablespace ID {} not found.", target_id)?;
130 }
131 }
132 }
133
134 Ok(())
135}
136
137fn find_ibd_and_ibu_files(datadir: &Path) -> Result<Vec<std::path::PathBuf>, IdbError> {
139 let mut files = Vec::new();
140
141 let entries = std::fs::read_dir(datadir)
142 .map_err(|e| IdbError::Io(format!("Cannot read directory {}: {}", datadir.display(), e)))?;
143
144 for entry in entries {
145 let entry =
146 entry.map_err(|e| IdbError::Io(format!("Cannot read directory entry: {}", e)))?;
147 let path = entry.path();
148
149 if path.is_dir() {
150 let sub_entries = std::fs::read_dir(&path)
151 .map_err(|e| IdbError::Io(format!("Cannot read {}: {}", path.display(), e)))?;
152
153 for sub_entry in sub_entries {
154 let sub_entry = sub_entry
155 .map_err(|e| IdbError::Io(format!("Cannot read directory entry: {}", e)))?;
156 let sub_path = sub_entry.path();
157 if let Some(ext) = sub_path.extension() {
158 if ext == "ibd" || ext == "ibu" {
159 files.push(sub_path);
160 }
161 }
162 }
163 } else if let Some(ext) = path.extension() {
164 if ext == "ibd" || ext == "ibu" {
165 files.push(path);
166 }
167 }
168 }
169
170 files.sort();
171 Ok(files)
172}