1use std::{borrow::Cow, fmt, str::FromStr};
2
3use postgres_protocol::escape::{escape_identifier, escape_literal};
4
5use super::sqlx;
6
7trait AsSql {
8 fn as_sql(&self) -> Cow<'_, str>;
9}
10
11pub async fn reload(pool: &sqlx::PgPool) -> Result<(), sqlx::Error> {
14 sqlx::query("SELECT pg_reload_conf()").execute(pool).await?;
15 Ok(())
16}
17
18pub enum AlterSystem<'a> {
19 Set(&'a Parameter<'a>, &'a Value),
20 Reset(&'a Parameter<'a>),
21 ResetAll,
22}
23
24impl AlterSystem<'_> {
25 pub async fn apply(&self, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> {
28 sqlx::query(&self.as_sql()).execute(pool).await?;
29 Ok(())
30 }
31}
32
33impl AsSql for AlterSystem<'_> {
34 fn as_sql(&self) -> Cow<'_, str> {
36 use AlterSystem::*;
37 match self {
38 Set(p, v) => format!("ALTER SYSTEM SET {} TO {}", p.as_sql(), v.as_sql()).into(),
39 Reset(p) => format!("ALTER SYSTEM RESET {}", p.as_sql()).into(),
40 ResetAll => "ALTER SYSTEM RESET ALL".into(),
41 }
42 }
43}
44
45#[derive(Debug, Clone, sqlx::FromRow)]
57pub struct Setting {
58 pub name: String,
59 pub setting: String,
60 pub unit: Option<String>,
61 pub category: String,
62 pub short_desc: String,
63 pub extra_desc: Option<String>,
64 pub context: String,
65 pub vartype: String,
66 pub source: String,
67 pub min_val: Option<String>,
68 pub max_val: Option<String>,
69 pub enumvals: Option<Vec<String>>,
70 pub boot_val: Option<String>,
71 pub reset_val: Option<String>,
72 pub sourcefile: Option<String>,
73 pub sourceline: Option<i32>,
74 pub pending_restart: bool,
75}
76
77impl Setting {
78 pub async fn list(pool: &sqlx::PgPool) -> Result<Vec<Self>, sqlx::Error> {
79 sqlx::query_as(
80 r"
81 SELECT
82 name,
83 setting,
84 unit,
85 category,
86 short_desc,
87 extra_desc,
88 context,
89 vartype,
90 source,
91 min_val,
92 max_val,
93 enumvals,
94 boot_val,
95 reset_val,
96 sourcefile,
97 sourceline,
98 pending_restart
99 FROM
100 pg_catalog.pg_settings
101 ",
102 )
103 .fetch_all(pool)
104 .await
105 }
106
107 pub async fn get<N: AsRef<str>>(
108 name: N,
109 pool: &sqlx::PgPool,
110 ) -> Result<Option<Self>, sqlx::Error> {
111 sqlx::query_as(
112 r"
113 SELECT
114 name,
115 setting,
116 unit,
117 category,
118 short_desc,
119 extra_desc,
120 context,
121 vartype,
122 source,
123 min_val,
124 max_val,
125 enumvals,
126 boot_val,
127 reset_val,
128 sourcefile,
129 sourceline,
130 pending_restart
131 FROM
132 pg_catalog.pg_settings
133 WHERE
134 name = $1
135 ",
136 )
137 .bind(name.as_ref())
138 .fetch_optional(pool)
139 .await
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub struct Parameter<'a>(pub &'a str);
145
146impl Parameter<'_> {
147 pub async fn get(&self, pool: &sqlx::PgPool) -> Result<Option<Value>, sqlx::Error> {
151 Setting::get(self.0, pool)
152 .await?
153 .map(|setting| {
154 Value::try_from(&setting)
155 .map_err(Into::into)
156 .map_err(sqlx::Error::Decode)
157 })
158 .transpose()
159 }
160
161 pub async fn set<V: Into<Value>>(
163 &self,
164 pool: &sqlx::PgPool,
165 value: V,
166 ) -> Result<(), sqlx::Error> {
167 let value = value.into();
168 AlterSystem::Set(self, &value).apply(pool).await?;
169 Ok(())
170 }
171
172 pub async fn reset(&self, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> {
174 AlterSystem::Reset(self).apply(pool).await?;
175 Ok(())
176 }
177}
178
179impl AsSql for Parameter<'_> {
180 fn as_sql(&self) -> Cow<'_, str> {
182 escape_identifier(self.0).into()
183 }
184}
185
186impl fmt::Display for Parameter<'_> {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 write!(f, "{}", self.0)
189 }
190}
191
192impl AsRef<str> for Parameter<'_> {
193 fn as_ref(&self) -> &str {
194 self.0
195 }
196}
197
198impl<'a> From<&'a str> for Parameter<'a> {
199 fn from(name: &'a str) -> Self {
200 Self(name)
201 }
202}
203
204impl<'a> From<&'a Setting> for Parameter<'a> {
205 fn from(setting: &'a Setting) -> Self {
206 Self(&setting.name)
207 }
208}
209
210#[derive(Debug, PartialEq)]
211pub enum Value {
212 Boolean(bool),
213 String(String), Number(String),
215 Memory(String, MemoryUnit),
216 Time(String, TimeUnit),
217}
218
219impl AsSql for Value {
220 fn as_sql(&self) -> Cow<'_, str> {
222 match self {
223 Value::Boolean(true) => "true".into(),
224 Value::Boolean(false) => "false".into(),
225 Value::String(value) => escape_literal(value).into(),
226 Value::Number(value) => value.into(),
227 Value::Memory(value, unit) => escape_literal(&format!("{value}{unit}")).into(),
228 Value::Time(value, unit) => escape_literal(&format!("{value}{unit}")).into(),
229 }
230 }
231}
232
233impl fmt::Display for Value {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 match self {
236 Value::Boolean(value) => write!(f, "{value}"),
237 Value::String(value) => write!(f, "{value}"),
238 Value::Number(value) => write!(f, "{value}"),
239 Value::Memory(value, unit) => write!(f, "{value}{unit}"),
240 Value::Time(value, unit) => write!(f, "{value}{unit}"),
241 }
242 }
243}
244
245impl From<bool> for Value {
246 fn from(value: bool) -> Self {
247 Value::Boolean(value)
248 }
249}
250
251impl From<&str> for Value {
252 fn from(value: &str) -> Self {
253 Value::String(value.to_owned())
254 }
255}
256
257impl From<String> for Value {
258 fn from(value: String) -> Self {
259 Value::String(value)
260 }
261}
262
263impl From<&String> for Value {
264 fn from(value: &String) -> Self {
265 Value::String(value.clone())
266 }
267}
268
269macro_rules! value_number_from {
270 ($($from_type:ty),*) => {
271 $(
272 impl From<$from_type> for Value {
273 fn from(number: $from_type) -> Self {
274 Value::Number(number.to_string())
275 }
276 }
277 )*
278 }
279}
280
281value_number_from!(i8, i16, i32, i64, i128);
282value_number_from!(u8, u16, u32, u64, u128);
283value_number_from!(f32, f64);
284value_number_from!(usize, isize);
285
286macro_rules! value_memory_from {
287 ($($from_type:ty),*) => {
288 $(
289 impl From<($from_type, MemoryUnit)> for Value {
290 fn from((number, unit): ($from_type, MemoryUnit)) -> Self {
291 Value::Memory(number.to_string(), unit)
292 }
293 }
294 )*
295 }
296}
297
298value_memory_from!(i8, i16, i32, i64, i128);
299value_memory_from!(u8, u16, u32, u64, u128);
300value_memory_from!(f32, f64);
301value_memory_from!(usize, isize);
302
303macro_rules! value_time_from {
304 ($($from_type:ty),*) => {
305 $(
306 impl From<($from_type, TimeUnit)> for Value {
307 fn from((number, unit): ($from_type, TimeUnit)) -> Self {
308 Value::Time(number.to_string(), unit)
309 }
310 }
311 )*
312 }
313}
314
315value_time_from!(i8, i16, i32, i64, i128);
316value_time_from!(u8, u16, u32, u64, u128);
317value_time_from!(f32, f64);
318value_time_from!(usize, isize);
319
320impl TryFrom<&Setting> for Value {
321 type Error = String;
322
323 fn try_from(setting: &Setting) -> Result<Self, Self::Error> {
324 Ok(match setting.vartype.as_ref() {
325 "bool" => match setting.setting.as_ref() {
326 "on" | "true" | "tru" | "tr" | "t" => Self::Boolean(true),
327 "yes" | "ye" | "y" | "1" => Self::Boolean(true),
328 "off" | "of" | "false" | "fals" | "fal" | "fa" | "f" => Self::Boolean(false),
329 "no" | "n" | "0" => Self::Boolean(false),
330 _ => return Err(format!("invalid boolean value: {setting:?}")),
331 },
332 "integer" | "real" => match setting.unit.as_deref() {
333 None => Self::Number(setting.setting.clone()),
334 Some("8kB" | "16MB") => Self::Number(setting.setting.clone()), Some(unit) => {
336 if let Ok(unit) = unit.parse::<MemoryUnit>() {
337 Self::Memory(setting.setting.clone(), unit)
338 } else if let Ok(unit) = unit.parse::<TimeUnit>() {
339 Self::Time(setting.setting.clone(), unit)
340 } else {
341 return Err(format!("invalid numeric value: {setting:?}"));
342 }
343 }
344 },
345 "string" => Self::String(setting.setting.clone()),
346 "enum" => Self::String(setting.setting.clone()),
347 _ => return Err(format!("unrecognised value type: {setting:?}")),
348 })
349 }
350}
351
352#[derive(Debug, Clone, Copy, PartialEq)]
355pub enum MemoryUnit {
356 Bytes,
357 Kibibytes,
358 Mebibytes,
359 Gibibytes,
360 Tebibytes,
361}
362
363impl fmt::Display for MemoryUnit {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 match self {
366 MemoryUnit::Bytes => write!(f, "B"),
367 MemoryUnit::Kibibytes => write!(f, "kB"),
368 MemoryUnit::Mebibytes => write!(f, "MB"),
369 MemoryUnit::Gibibytes => write!(f, "GB"),
370 MemoryUnit::Tebibytes => write!(f, "TB"),
371 }
372 }
373}
374
375impl FromStr for MemoryUnit {
376 type Err = String;
377
378 fn from_str(s: &str) -> Result<Self, Self::Err> {
379 match s {
380 "B" => Ok(MemoryUnit::Bytes),
381 "kB" => Ok(MemoryUnit::Kibibytes),
382 "MB" => Ok(MemoryUnit::Mebibytes),
383 "GB" => Ok(MemoryUnit::Gibibytes),
384 "TB" => Ok(MemoryUnit::Tebibytes),
385 _ => Err(format!("invalid memory unit: {s:?}")),
386 }
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq)]
393pub enum TimeUnit {
394 Microseconds,
395 Milliseconds,
396 Seconds,
397 Minutes,
398 Hours,
399 Days,
400}
401
402impl fmt::Display for TimeUnit {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 match self {
405 TimeUnit::Microseconds => write!(f, "us"),
406 TimeUnit::Milliseconds => write!(f, "ms"),
407 TimeUnit::Seconds => write!(f, "s"),
408 TimeUnit::Minutes => write!(f, "min"),
409 TimeUnit::Hours => write!(f, "h"),
410 TimeUnit::Days => write!(f, "d"),
411 }
412 }
413}
414
415impl FromStr for TimeUnit {
416 type Err = String;
417
418 fn from_str(s: &str) -> Result<Self, Self::Err> {
419 match s {
420 "us" => Ok(TimeUnit::Microseconds),
421 "ms" => Ok(TimeUnit::Milliseconds),
422 "s" => Ok(TimeUnit::Seconds),
423 "min" => Ok(TimeUnit::Minutes),
424 "h" => Ok(TimeUnit::Hours),
425 "d" => Ok(TimeUnit::Days),
426 _ => Err(format!("invalid time unit: {s:?}")),
427 }
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use paste::paste;
434
435 use super::{
436 AsSql,
437 MemoryUnit::{self, *},
438 Parameter,
439 TimeUnit::{self, *},
440 Value,
441 };
442
443 #[test]
444 fn test_parameter_as_sql() {
445 assert_eq!(Parameter("foo").as_sql(), "\"foo\"");
446 assert_eq!(Parameter("foo \\bar").as_sql(), "\"foo \\bar\"");
447 assert_eq!(Parameter("foo\"bar").as_sql(), "\"foo\"\"bar\"");
448 }
449
450 #[test]
451 fn test_value_as_sql_bool() {
452 assert_eq!(Value::Boolean(false).as_sql(), "false");
453 assert_eq!(Value::Boolean(true).as_sql(), "true");
454 }
455
456 #[test]
457 fn test_value_as_sql_string() {
458 assert_eq!(Value::from("foo").as_sql(), "'foo'");
459 assert_eq!(Value::from("foo \\bar").as_sql(), " E'foo \\\\bar'");
460 assert_eq!(Value::from("foo'\"'bar").as_sql(), "'foo''\"''bar'");
461 }
462
463 #[test]
464 fn test_value_as_sql_number() {
465 assert_eq!(Value::Number("123".into()).as_sql(), "123");
471 assert_eq!(Value::Number("123.456".into()).as_sql(), "123.456");
472 }
473
474 #[test]
475 fn test_value_as_sql_memory() {
476 assert_eq!(
477 Value::Memory("123.4".into(), Gibibytes).as_sql(),
478 "'123.4GB'",
479 );
480 }
481
482 #[test]
483 fn test_value_as_sql_time() {
484 assert_eq!(Value::Time("123.4".into(), Hours).as_sql(), "'123.4h'",);
485 }
486
487 macro_rules! test_value_number_from {
488 ($($from_type:ty),*) => {
489 $(
490 paste! {
491 #[test]
492 #[allow(clippy::cast_precision_loss, clippy::cast_lossless)]
493 fn [< test_value_number_from_ $from_type >]() {
494 assert_eq!(Value::from(42 as $from_type), Value::Number("42".into()));
495 }
496 }
497 )*
498 }
499 }
500
501 test_value_number_from!(i8, i16, i32, i64, i128);
502 test_value_number_from!(u8, u16, u32, u64, u128);
503 test_value_number_from!(f32, f64);
504 test_value_number_from!(usize, isize);
505
506 #[test]
507 fn test_memory_unit_roundtrip() {
508 let units = &[Bytes, Kibibytes, Mebibytes, Gibibytes, Tebibytes];
509 for unit in units {
510 assert_eq!(format!("{unit}").parse::<MemoryUnit>(), Ok(*unit));
511 }
512 }
513
514 #[test]
515 fn test_time_unit_roundtrip() {
516 let units = &[Microseconds, Milliseconds, Seconds, Minutes, Hours, Days];
517 for unit in units {
518 assert_eq!(format!("{unit}").parse::<TimeUnit>(), Ok(*unit));
519 }
520 }
521}