1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path::Path;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum GlobalTableMode {
14 None,
16 #[default]
18 Lookups,
19 All,
21}
22
23impl std::str::FromStr for GlobalTableMode {
24 type Err = String;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 match s.to_lowercase().as_str() {
28 "none" => Ok(GlobalTableMode::None),
29 "lookups" => Ok(GlobalTableMode::Lookups),
30 "all" => Ok(GlobalTableMode::All),
31 _ => Err(format!(
32 "Unknown global mode: {}. Valid options: none, lookups, all",
33 s
34 )),
35 }
36 }
37}
38
39impl std::fmt::Display for GlobalTableMode {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 GlobalTableMode::None => write!(f, "none"),
43 GlobalTableMode::Lookups => write!(f, "lookups"),
44 GlobalTableMode::All => write!(f, "all"),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
51#[serde(rename_all = "lowercase")]
52pub enum TableClassification {
53 #[default]
55 Normal,
56 Root,
58 Lookup,
60 System,
62 Junction,
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68#[serde(default)]
69pub struct TableConfig {
70 pub percent: Option<u32>,
72 pub rows: Option<usize>,
74 pub skip: bool,
76 pub classification: Option<TableClassification>,
78}
79
80#[derive(Debug, Clone, Default, Serialize, Deserialize)]
82#[serde(default)]
83pub struct DefaultConfig {
84 pub percent: Option<u32>,
86 pub rows: Option<usize>,
88}
89
90#[derive(Debug, Clone, Default, Serialize, Deserialize)]
92#[serde(default)]
93pub struct ClassificationConfig {
94 #[serde(default)]
96 pub global: Vec<String>,
97 #[serde(default)]
99 pub system: Vec<String>,
100 #[serde(default)]
102 pub lookup: Vec<String>,
103 #[serde(default)]
105 pub root: Vec<String>,
106}
107
108#[derive(Debug, Clone, Default, Serialize, Deserialize)]
110#[serde(default)]
111pub struct SampleYamlConfig {
112 pub default: DefaultConfig,
114 pub classification: ClassificationConfig,
116 #[serde(default)]
118 pub tables: HashMap<String, TableConfig>,
119}
120
121impl SampleYamlConfig {
122 pub fn load(path: &Path) -> anyhow::Result<Self> {
124 let content = fs::read_to_string(path)?;
125 let config: SampleYamlConfig = serde_yaml::from_str(&content)?;
126 Ok(config)
127 }
128
129 pub fn get_table_config(&self, table_name: &str) -> Option<&TableConfig> {
131 self.tables.get(table_name).or_else(|| {
132 let lower = table_name.to_lowercase();
134 self.tables
135 .iter()
136 .find(|(k, _)| k.to_lowercase() == lower)
137 .map(|(_, v)| v)
138 })
139 }
140
141 pub fn get_classification(&self, table_name: &str) -> TableClassification {
143 if let Some(config) = self.get_table_config(table_name) {
145 if let Some(class) = config.classification {
146 return class;
147 }
148 }
149
150 let lower = table_name.to_lowercase();
151
152 if self
154 .classification
155 .global
156 .iter()
157 .any(|t| t.to_lowercase() == lower)
158 {
159 return TableClassification::Lookup;
160 }
161 if self
162 .classification
163 .system
164 .iter()
165 .any(|t| t.to_lowercase() == lower)
166 {
167 return TableClassification::System;
168 }
169 if self
170 .classification
171 .lookup
172 .iter()
173 .any(|t| t.to_lowercase() == lower)
174 {
175 return TableClassification::Lookup;
176 }
177 if self
178 .classification
179 .root
180 .iter()
181 .any(|t| t.to_lowercase() == lower)
182 {
183 return TableClassification::Root;
184 }
185
186 TableClassification::Normal
187 }
188
189 pub fn should_skip(&self, table_name: &str) -> bool {
191 if let Some(config) = self.get_table_config(table_name) {
192 return config.skip;
193 }
194 false
195 }
196
197 pub fn get_percent(&self, table_name: &str) -> Option<u32> {
199 if let Some(config) = self.get_table_config(table_name) {
200 if config.percent.is_some() {
201 return config.percent;
202 }
203 }
204 self.default.percent
205 }
206
207 pub fn get_rows(&self, table_name: &str) -> Option<usize> {
209 if let Some(config) = self.get_table_config(table_name) {
210 if config.rows.is_some() {
211 return config.rows;
212 }
213 }
214 self.default.rows
215 }
216}
217
218pub struct DefaultClassifier;
220
221impl DefaultClassifier {
222 const SYSTEM_PATTERNS: &'static [&'static str] = &[
224 "migrations",
225 "failed_jobs",
226 "job_batches",
227 "jobs",
228 "cache",
229 "cache_locks",
230 "sessions",
231 "password_reset_tokens",
232 "personal_access_tokens",
233 "telescope_entries",
234 "telescope_entries_tags",
235 "telescope_monitoring",
236 "pulse_",
237 "horizon_",
238 ];
239
240 const LOOKUP_PATTERNS: &'static [&'static str] = &[
242 "countries",
243 "states",
244 "provinces",
245 "cities",
246 "currencies",
247 "languages",
248 "timezones",
249 "permissions",
250 "roles",
251 "settings",
252 ];
253
254 pub fn classify(table_name: &str) -> TableClassification {
256 let lower = table_name.to_lowercase();
257
258 for pattern in Self::SYSTEM_PATTERNS {
260 if lower.starts_with(pattern) || lower == *pattern {
261 return TableClassification::System;
262 }
263 }
264
265 for pattern in Self::LOOKUP_PATTERNS {
267 if lower == *pattern {
268 return TableClassification::Lookup;
269 }
270 }
271
272 TableClassification::Normal
273 }
274}