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