1use std::cmp::Ordering;
8use std::collections::BTreeMap;
9use std::hash::{Hash, Hasher};
10use std::str::FromStr;
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
19pub struct NotNanF64(f64);
20
21impl NotNanF64 {
22 pub const fn new(v: f64) -> Result<Self, f64> {
28 if v.is_nan() { Err(v) } else { Ok(Self(v)) }
29 }
30
31 #[must_use]
33 pub const fn get(self) -> f64 {
34 self.0
35 }
36}
37
38impl PartialEq for NotNanF64 {
39 fn eq(&self, other: &Self) -> bool {
40 self.0.to_bits() == other.0.to_bits()
41 }
42}
43impl Eq for NotNanF64 {}
44impl Hash for NotNanF64 {
45 fn hash<H: Hasher>(&self, state: &mut H) {
46 self.0.to_bits().hash(state);
47 }
48}
49impl PartialOrd for NotNanF64 {
50 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
51 Some(self.cmp(other))
52 }
53}
54impl Ord for NotNanF64 {
55 fn cmp(&self, other: &Self) -> Ordering {
56 self.0.partial_cmp(&other.0).unwrap_or(Ordering::Equal)
58 }
59}
60
61#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
63pub struct AutovacuumOptions {
64 pub enabled: Option<bool>,
66 pub vacuum_threshold: Option<u64>,
68 pub vacuum_scale_factor: Option<NotNanF64>,
70 pub vacuum_cost_delay: Option<u64>,
72 pub vacuum_cost_limit: Option<u64>,
74 pub analyze_threshold: Option<u64>,
76 pub analyze_scale_factor: Option<NotNanF64>,
78 pub freeze_max_age: Option<u64>,
80 pub freeze_min_age: Option<u64>,
82 pub freeze_table_age: Option<u64>,
84 pub multixact_freeze_max_age: Option<u64>,
86 pub multixact_freeze_min_age: Option<u64>,
88 pub multixact_freeze_table_age: Option<u64>,
90 pub vacuum_insert_threshold: Option<u64>,
92 pub vacuum_insert_scale_factor: Option<NotNanF64>,
94 pub log_min_duration: Option<i64>,
96}
97
98impl AutovacuumOptions {
99 #[must_use]
101 pub const fn is_empty(&self) -> bool {
102 self.enabled.is_none()
103 && self.vacuum_threshold.is_none()
104 && self.vacuum_scale_factor.is_none()
105 && self.vacuum_cost_delay.is_none()
106 && self.vacuum_cost_limit.is_none()
107 && self.analyze_threshold.is_none()
108 && self.analyze_scale_factor.is_none()
109 && self.freeze_max_age.is_none()
110 && self.freeze_min_age.is_none()
111 && self.freeze_table_age.is_none()
112 && self.multixact_freeze_max_age.is_none()
113 && self.multixact_freeze_min_age.is_none()
114 && self.multixact_freeze_table_age.is_none()
115 && self.vacuum_insert_threshold.is_none()
116 && self.vacuum_insert_scale_factor.is_none()
117 && self.log_min_duration.is_none()
118 }
119}
120
121#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
124pub struct TableStorageOptions {
125 pub fillfactor: Option<u32>,
127 pub autovacuum: AutovacuumOptions,
129 pub parallel_workers: Option<u32>,
131 pub toast_tuple_target: Option<u32>,
133 pub user_catalog_table: Option<bool>,
135 pub vacuum_truncate: Option<bool>,
137 pub extra: BTreeMap<String, String>,
139}
140
141impl TableStorageOptions {
142 #[must_use]
144 pub fn is_empty(&self) -> bool {
145 self.fillfactor.is_none()
146 && self.autovacuum.is_empty()
147 && self.parallel_workers.is_none()
148 && self.toast_tuple_target.is_none()
149 && self.user_catalog_table.is_none()
150 && self.vacuum_truncate.is_none()
151 && self.extra.is_empty()
152 }
153}
154
155pub type MaterializedViewStorageOptions = TableStorageOptions;
157
158#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
161pub struct IndexStorageOptions {
162 pub fillfactor: Option<u32>,
164 pub fastupdate: Option<bool>,
166 pub gin_pending_list_limit: Option<u64>,
168 pub buffering: Option<BufferingMode>,
170 pub deduplicate_items: Option<bool>,
172 pub pages_per_range: Option<u32>,
174 pub autosummarize: Option<bool>,
176 pub extra: BTreeMap<String, String>,
178}
179
180impl IndexStorageOptions {
181 #[must_use]
183 pub fn is_empty(&self) -> bool {
184 self.fillfactor.is_none()
185 && self.fastupdate.is_none()
186 && self.gin_pending_list_limit.is_none()
187 && self.buffering.is_none()
188 && self.deduplicate_items.is_none()
189 && self.pages_per_range.is_none()
190 && self.autosummarize.is_none()
191 && self.extra.is_empty()
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
197#[serde(rename_all = "snake_case")]
198pub enum BufferingMode {
199 On,
201 Off,
203 Auto,
205}
206
207impl BufferingMode {
208 #[must_use]
210 pub const fn sql_keyword(self) -> &'static str {
211 match self {
212 Self::On => "on",
213 Self::Off => "off",
214 Self::Auto => "auto",
215 }
216 }
217}
218
219impl FromStr for BufferingMode {
220 type Err = ();
221
222 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 match s.to_ascii_lowercase().as_str() {
225 "on" => Ok(Self::On),
226 "off" => Ok(Self::Off),
227 "auto" => Ok(Self::Auto),
228 _ => Err(()),
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn not_nan_rejects_nan() {
239 assert!(NotNanF64::new(f64::NAN).is_err());
240 assert!(NotNanF64::new(1.5).is_ok());
241 assert!(NotNanF64::new(0.0).is_ok());
242 assert!(NotNanF64::new(f64::INFINITY).is_ok());
243 }
244
245 #[test]
246 fn not_nan_equality_is_bit_exact() {
247 let a = NotNanF64::new(0.1).unwrap();
248 let b = NotNanF64::new(0.1).unwrap();
249 assert_eq!(a, b);
250 }
251
252 #[test]
253 fn default_storage_is_empty() {
254 assert!(TableStorageOptions::default().is_empty());
255 assert!(IndexStorageOptions::default().is_empty());
256 assert!(AutovacuumOptions::default().is_empty());
257 }
258
259 #[test]
260 fn non_empty_storage_detected() {
261 let s = TableStorageOptions {
262 fillfactor: Some(80),
263 ..Default::default()
264 };
265 assert!(!s.is_empty());
266 }
267
268 #[test]
269 fn buffering_mode_roundtrips() {
270 for m in [BufferingMode::On, BufferingMode::Off, BufferingMode::Auto] {
271 assert_eq!(m.sql_keyword().parse::<BufferingMode>(), Ok(m));
272 }
273 assert!("bogus".parse::<BufferingMode>().is_err());
274 }
275
276 #[test]
277 fn extra_is_sorted_via_btreemap() {
278 let mut s = TableStorageOptions::default();
279 s.extra.insert("zebra".into(), "1".into());
280 s.extra.insert("alpha".into(), "2".into());
281 let keys: Vec<_> = s.extra.keys().cloned().collect();
282 assert_eq!(keys, vec!["alpha", "zebra"]);
283 }
284}