1use std::any::Any;
11use std::borrow::Cow;
12use std::fmt::{self, Debug};
13use std::num::NonZeroU32;
14use std::str::FromStr;
15use std::time::Duration;
16
17use chrono::{DateTime, Utc};
18use ipnet::IpNet;
19use itertools::Itertools;
20use mz_pgwire_common::Severity;
21use mz_repr::adt::numeric::Numeric;
22use mz_repr::adt::timestamp::CheckedTimestamp;
23use mz_repr::strconv;
24use mz_rocksdb_types::config::{CompactionStyle, CompressionType};
25use mz_sql_parser::ast::{Ident, TransactionIsolationLevel};
26use mz_tracing::{CloneableEnvFilter, SerializableDirective};
27use serde::{Deserialize, Serialize};
28use uncased::UncasedStr;
29
30use super::VarInput;
31use super::errors::VarParseError;
32
33pub trait Value: Any + AsAny + Debug + Send + Sync {
39 fn type_name() -> Cow<'static, str>
40 where
41 Self: Sized;
42
43 fn parse(input: VarInput) -> Result<Self, VarParseError>
44 where
45 Self: Sized;
46
47 fn format(&self) -> String;
48
49 fn box_clone(&self) -> Box<dyn Value>;
50
51 fn parse_dyn_value(input: VarInput) -> Result<Box<dyn Value>, VarParseError>
53 where
54 Self: Sized,
55 {
56 Self::parse(input).map(|val| {
57 let dyn_val: Box<dyn Value> = Box::new(val);
58 dyn_val
59 })
60 }
61}
62
63impl PartialEq for Box<dyn Value> {
66 fn eq(&self, other: &Box<dyn Value>) -> bool {
67 self.format().eq(&other.format())
68 }
69}
70impl Eq for Box<dyn Value> {}
71
72impl<'a> PartialEq for &'a dyn Value {
73 fn eq(&self, other: &&dyn Value) -> bool {
74 self.format().eq(&other.format())
75 }
76}
77impl<'a> Eq for &'a dyn Value {}
78
79pub trait AsAny {
88 fn as_any(&self) -> &dyn Any;
89}
90
91impl<T: Any> AsAny for T {
92 fn as_any(&self) -> &dyn Any {
93 self
94 }
95}
96
97fn extract_single_value(input: VarInput<'_>) -> Result<&str, VarParseError> {
99 match input {
100 VarInput::Flat(value) => Ok(value),
101 VarInput::SqlSet([value]) => Ok(value),
102 VarInput::SqlSet(values) => Err(VarParseError::InvalidParameterValue {
103 invalid_values: values.to_vec(),
104 reason: "expects a single value".into(),
105 }),
106 }
107}
108
109impl<V: Value + Clone> Value for Option<V> {
110 fn type_name() -> Cow<'static, str>
111 where
112 Self: Sized,
113 {
114 format!("optional {}", V::type_name()).into()
115 }
116
117 fn parse(input: VarInput) -> Result<Self, VarParseError>
118 where
119 Self: Sized,
120 {
121 let s = extract_single_value(input)?;
122 match s {
123 "" => Ok(None),
124 _ => <V as Value>::parse(VarInput::Flat(s)).map(Some),
125 }
126 }
127
128 fn box_clone(&self) -> Box<dyn Value> {
129 Box::new(self.clone())
130 }
131
132 fn format(&self) -> String {
133 match self {
134 Some(s) => s.format(),
135 None => "".to_string(),
136 }
137 }
138}
139
140impl Value for String {
141 fn type_name() -> Cow<'static, str>
142 where
143 Self: Sized,
144 {
145 "string".into()
146 }
147
148 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
149 where
150 Self: Sized,
151 {
152 let s = extract_single_value(input)?;
153 Ok(s.to_string())
154 }
155
156 fn box_clone(&self) -> Box<dyn Value> {
157 Box::new(self.clone())
158 }
159
160 fn format(&self) -> String {
161 self.to_string()
162 }
163}
164
165impl Value for Cow<'static, str> {
166 fn type_name() -> Cow<'static, str>
167 where
168 Self: Sized,
169 {
170 "string".into()
171 }
172
173 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
174 where
175 Self: Sized,
176 {
177 let s = extract_single_value(input)?;
178 Ok(s.to_string().into())
179 }
180
181 fn box_clone(&self) -> Box<dyn Value> {
182 Box::new(self.clone())
183 }
184
185 fn format(&self) -> String {
186 self.to_string()
187 }
188}
189
190impl Value for bool {
191 fn type_name() -> Cow<'static, str>
192 where
193 Self: Sized,
194 {
195 "boolean".into()
196 }
197
198 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
199 where
200 Self: Sized,
201 {
202 let s = extract_single_value(input)?;
203 match s {
204 "t" | "true" | "on" => Ok(true),
205 "f" | "false" | "off" => Ok(false),
206 _ => Err(VarParseError::InvalidParameterType),
207 }
208 }
209
210 fn box_clone(&self) -> Box<dyn Value> {
211 Box::new(self.clone())
212 }
213
214 fn format(&self) -> String {
215 match self {
216 true => "on".into(),
217 false => "off".into(),
218 }
219 }
220}
221
222impl Value for Option<CheckedTimestamp<DateTime<Utc>>> {
223 fn type_name() -> Cow<'static, str>
224 where
225 Self: Sized,
226 {
227 "timestamptz".into()
228 }
229
230 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
231 where
232 Self: Sized,
233 {
234 let s = extract_single_value(input)?;
235 strconv::parse_timestamptz(s)
236 .map_err(|_| VarParseError::InvalidParameterType)
237 .map(Some)
238 }
239
240 fn box_clone(&self) -> Box<dyn Value> {
241 Box::new(self.clone())
242 }
243
244 fn format(&self) -> String {
245 self.map(|t| t.to_string()).unwrap_or_default()
246 }
247}
248
249impl Value for Numeric {
250 fn type_name() -> Cow<'static, str>
251 where
252 Self: Sized,
253 {
254 "numeric".into()
255 }
256
257 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
258 where
259 Self: Sized,
260 {
261 let s = extract_single_value(input)?;
262 s.parse::<Numeric>()
263 .map_err(|_| VarParseError::InvalidParameterType)
264 }
265
266 fn box_clone(&self) -> Box<dyn Value> {
267 Box::new(self.clone())
268 }
269
270 fn format(&self) -> String {
271 self.to_standard_notation_string()
272 }
273}
274
275const SEC_TO_MIN: u64 = 60u64;
276const SEC_TO_HOUR: u64 = 60u64 * 60;
277const SEC_TO_DAY: u64 = 60u64 * 60 * 24;
278const MICRO_TO_MILLI: u32 = 1000u32;
279
280impl Value for Duration {
281 fn type_name() -> Cow<'static, str>
282 where
283 Self: Sized,
284 {
285 "duration".into()
286 }
287
288 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
289 where
290 Self: Sized,
291 {
292 let s = extract_single_value(input)?;
293 let s = s.trim();
294 let split_pos = s.find(|p: char| !p.is_ascii_digit()).unwrap_or(s.len());
300
301 let d = s[..split_pos]
303 .parse::<u64>()
304 .map_err(|_| VarParseError::InvalidParameterType)?;
305
306 let (f, m): (fn(u64) -> Duration, u64) = match s[split_pos..].trim_start() {
308 "us" => (Duration::from_micros, 1),
309 "ms" | "" => (Duration::from_millis, 1),
311 "s" => (Duration::from_secs, 1),
312 "min" => (Duration::from_secs, SEC_TO_MIN),
313 "h" => (Duration::from_secs, SEC_TO_HOUR),
314 "d" => (Duration::from_secs, SEC_TO_DAY),
315 o => {
316 return Err(VarParseError::InvalidParameterValue {
317 invalid_values: vec![o.to_string()],
318 reason: format!("expected us, ms, s, min, h, or d but got {o:?}"),
319 });
320 }
321 };
322
323 let d = f(d
324 .checked_mul(m)
325 .ok_or_else(|| VarParseError::InvalidParameterValue {
326 invalid_values: vec![s.to_string()],
327 reason: "expected value to fit in u64".into(),
328 })?);
329 Ok(d)
330 }
331
332 fn box_clone(&self) -> Box<dyn Value> {
333 Box::new(self.clone())
334 }
335
336 fn format(&self) -> String {
341 let micros = self.subsec_micros();
342 if micros > 0 {
343 match micros {
344 ms if ms != 0 && ms % MICRO_TO_MILLI == 0 => {
345 format!(
346 "{} ms",
347 self.as_secs() * 1000 + u64::from(ms / MICRO_TO_MILLI)
348 )
349 }
350 us => format!("{} us", self.as_secs() * 1_000_000 + u64::from(us)),
351 }
352 } else {
353 match self.as_secs() {
354 zero if zero == u64::MAX => "0".to_string(),
355 d if d != 0 && d % SEC_TO_DAY == 0 => format!("{} d", d / SEC_TO_DAY),
356 h if h != 0 && h % SEC_TO_HOUR == 0 => format!("{} h", h / SEC_TO_HOUR),
357 m if m != 0 && m % SEC_TO_MIN == 0 => format!("{} min", m / SEC_TO_MIN),
358 s => format!("{} s", s),
359 }
360 }
361 }
362}
363
364impl Value for serde_json::Value {
365 fn type_name() -> Cow<'static, str>
366 where
367 Self: Sized,
368 {
369 "jsonb".into()
370 }
371
372 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
373 where
374 Self: Sized,
375 {
376 let s = extract_single_value(input)?;
377 serde_json::from_str(s).map_err(|_| VarParseError::InvalidParameterType)
378 }
379
380 fn box_clone(&self) -> Box<dyn Value> {
381 Box::new(self.clone())
382 }
383
384 fn format(&self) -> String {
385 self.to_string()
386 }
387}
388
389#[derive(Debug, Clone, Eq, PartialEq)]
392pub struct DateStyle(pub [&'static str; 2]);
393
394pub static DEFAULT_DATE_STYLE: DateStyle = DateStyle(["ISO", "MDY"]);
395
396impl Value for DateStyle {
397 fn type_name() -> Cow<'static, str>
398 where
399 Self: Sized,
400 {
401 "string list".into()
402 }
403
404 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
406 where
407 Self: Sized,
408 {
409 let input = match input {
410 VarInput::Flat(v) => mz_sql_parser::parser::split_identifier_string(v)
411 .map_err(|_| VarParseError::InvalidParameterType)?,
412 VarInput::SqlSet(values) => {
415 let mut out = vec![];
416 for v in values {
417 let idents = mz_sql_parser::parser::split_identifier_string(v)
418 .map_err(|_| VarParseError::InvalidParameterType)?;
419 out.extend(idents)
420 }
421 out
422 }
423 };
424
425 for input in input {
426 if !DEFAULT_DATE_STYLE
427 .0
428 .iter()
429 .any(|valid| UncasedStr::new(valid) == &input)
430 {
431 return Err(VarParseError::FixedValueParameter);
432 }
433 }
434
435 Ok(DEFAULT_DATE_STYLE.clone())
436 }
437
438 fn box_clone(&self) -> Box<dyn Value> {
439 Box::new(self.clone())
440 }
441
442 fn format(&self) -> String {
443 self.0.join(", ")
444 }
445}
446
447impl Value for Vec<Ident> {
448 fn type_name() -> Cow<'static, str>
449 where
450 Self: Sized,
451 {
452 "identifier list".into()
453 }
454
455 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
456 where
457 Self: Sized,
458 {
459 let holder;
460 let values = match input {
461 VarInput::Flat(value) => {
462 holder = mz_sql_parser::parser::split_identifier_string(value)
463 .map_err(|_| VarParseError::InvalidParameterType)?;
464 &holder
465 }
466 VarInput::SqlSet(values) => values,
469 };
470 if values.iter().any(|v| v.is_empty()) {
474 if values.len() == 1 {
475 return Ok(vec![]);
476 }
477 return Err(VarParseError::InvalidParameterValue {
478 invalid_values: values.to_vec(),
479 reason: "empty identifier".into(),
480 });
481 }
482 let values = values
483 .iter()
484 .map(Ident::new)
485 .collect::<Result<_, _>>()
486 .map_err(|e| VarParseError::InvalidParameterValue {
487 invalid_values: values.to_vec(),
488 reason: e.to_string(),
489 })?;
490 Ok(values)
491 }
492
493 fn box_clone(&self) -> Box<dyn Value> {
494 Box::new(self.clone())
495 }
496
497 fn format(&self) -> String {
498 self.iter().map(|ident| ident.to_string()).join(", ")
499 }
500}
501
502impl Value for Vec<IpNet> {
503 fn type_name() -> Cow<'static, str>
504 where
505 Self: Sized,
506 {
507 "CIDR list".into()
508 }
509
510 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
511 where
512 Self: Sized,
513 {
514 let values = input.to_vec();
515 let values: Vec<IpNet> = values
516 .iter()
517 .flat_map(|i| i.split(','))
518 .map(|d| IpNet::from_str(d.trim()))
519 .collect::<Result<_, _>>()
520 .map_err(|e| VarParseError::InvalidParameterValue {
521 invalid_values: values,
522 reason: e.to_string(),
523 })?;
524 Ok(values)
525 }
526
527 fn box_clone(&self) -> Box<dyn Value> {
528 Box::new(self.clone())
529 }
530
531 fn format(&self) -> String {
532 self.iter().map(|ident| ident.to_string()).join(", ")
533 }
534}
535
536impl Value for Vec<SerializableDirective> {
537 fn type_name() -> Cow<'static, str>
538 where
539 Self: Sized,
540 {
541 "directive list".into()
542 }
543
544 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
545 where
546 Self: Sized,
547 {
548 let values = input.to_vec();
549 let dirs: Result<_, _> = values
550 .iter()
551 .flat_map(|i| i.split(','))
552 .map(|d| SerializableDirective::from_str(d.trim()))
553 .collect();
554 dirs.map_err(|e| VarParseError::InvalidParameterValue {
555 invalid_values: values.to_vec(),
556 reason: e.to_string(),
557 })
558 }
559
560 fn box_clone(&self) -> Box<dyn Value> {
561 Box::new(self.clone())
562 }
563
564 fn format(&self) -> String {
565 self.iter().map(|d| d.to_string()).join(", ")
566 }
567}
568
569#[derive(Clone, Debug, Eq, PartialEq)]
571pub struct Failpoints;
572
573impl Value for Failpoints {
574 fn type_name() -> Cow<'static, str>
575 where
576 Self: Sized,
577 {
578 "failpoints config".into()
579 }
580
581 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
582 where
583 Self: Sized,
584 {
585 let values = input.to_vec();
586 for mut cfg in values.iter().map(|v| v.trim().split(';')).flatten() {
587 cfg = cfg.trim();
588 if cfg.is_empty() {
589 continue;
590 }
591 let mut splits = cfg.splitn(2, '=');
592 let failpoint = splits
593 .next()
594 .ok_or_else(|| VarParseError::InvalidParameterValue {
595 invalid_values: input.to_vec(),
596 reason: "missing failpoint name".into(),
597 })?;
598 let action = splits
599 .next()
600 .ok_or_else(|| VarParseError::InvalidParameterValue {
601 invalid_values: input.to_vec(),
602 reason: "missing failpoint action".into(),
603 })?;
604 fail::cfg(failpoint, action).map_err(|e| VarParseError::InvalidParameterValue {
608 invalid_values: input.to_vec(),
609 reason: e.to_string(),
610 })?;
611 }
612
613 Ok(Failpoints)
614 }
615
616 fn box_clone(&self) -> Box<dyn Value> {
617 Box::new(self.clone())
618 }
619
620 fn format(&self) -> String {
621 "<omitted>".to_string()
622 }
623}
624
625#[derive(Clone, Copy, Debug, Eq, PartialEq)]
633pub enum ClientSeverity {
634 Error,
636 Warning,
638 Notice,
640 Log,
642 Debug1,
644 Debug2,
646 Debug3,
648 Debug4,
650 Debug5,
652 Info,
655}
656
657impl Serialize for ClientSeverity {
658 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
659 where
660 S: serde::Serializer,
661 {
662 serializer.serialize_str(self.as_str())
663 }
664}
665
666impl ClientSeverity {
667 fn as_str(&self) -> &'static str {
668 match self {
669 ClientSeverity::Error => "error",
670 ClientSeverity::Warning => "warning",
671 ClientSeverity::Notice => "notice",
672 ClientSeverity::Info => "info",
673 ClientSeverity::Log => "log",
674 ClientSeverity::Debug1 => "debug1",
675 ClientSeverity::Debug2 => "debug2",
676 ClientSeverity::Debug3 => "debug3",
677 ClientSeverity::Debug4 => "debug4",
678 ClientSeverity::Debug5 => "debug5",
679 }
680 }
681
682 fn valid_values() -> Vec<&'static str> {
683 vec![
685 ClientSeverity::Debug5.as_str(),
686 ClientSeverity::Debug4.as_str(),
687 ClientSeverity::Debug3.as_str(),
688 ClientSeverity::Debug2.as_str(),
689 ClientSeverity::Debug1.as_str(),
690 ClientSeverity::Log.as_str(),
691 ClientSeverity::Notice.as_str(),
692 ClientSeverity::Warning.as_str(),
693 ClientSeverity::Error.as_str(),
694 ]
695 }
696
697 pub fn should_output_to_client(&self, severity: &Severity) -> bool {
708 match (self, severity) {
709 (_, Severity::Info) => true,
711 (ClientSeverity::Error, Severity::Error | Severity::Fatal | Severity::Panic) => true,
712 (
713 ClientSeverity::Warning,
714 Severity::Error | Severity::Fatal | Severity::Panic | Severity::Warning,
715 ) => true,
716 (
717 ClientSeverity::Notice,
718 Severity::Error
719 | Severity::Fatal
720 | Severity::Panic
721 | Severity::Warning
722 | Severity::Notice,
723 ) => true,
724 (
725 ClientSeverity::Info,
726 Severity::Error
727 | Severity::Fatal
728 | Severity::Panic
729 | Severity::Warning
730 | Severity::Notice,
731 ) => true,
732 (
733 ClientSeverity::Log,
734 Severity::Error
735 | Severity::Fatal
736 | Severity::Panic
737 | Severity::Warning
738 | Severity::Notice
739 | Severity::Log,
740 ) => true,
741 (
742 ClientSeverity::Debug1
743 | ClientSeverity::Debug2
744 | ClientSeverity::Debug3
745 | ClientSeverity::Debug4
746 | ClientSeverity::Debug5,
747 _,
748 ) => true,
749
750 (
751 ClientSeverity::Error,
752 Severity::Warning | Severity::Notice | Severity::Log | Severity::Debug,
753 ) => false,
754 (ClientSeverity::Warning, Severity::Notice | Severity::Log | Severity::Debug) => false,
755 (ClientSeverity::Notice, Severity::Log | Severity::Debug) => false,
756 (ClientSeverity::Info, Severity::Log | Severity::Debug) => false,
757 (ClientSeverity::Log, Severity::Debug) => false,
758 }
759 }
760}
761
762impl Value for ClientSeverity {
763 fn type_name() -> Cow<'static, str>
764 where
765 Self: Sized,
766 {
767 "string".into()
768 }
769
770 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
771 where
772 Self: Sized,
773 {
774 let s = extract_single_value(input)?;
775 let s = UncasedStr::new(s);
776
777 if s == ClientSeverity::Error.as_str() {
778 Ok(ClientSeverity::Error)
779 } else if s == ClientSeverity::Warning.as_str() {
780 Ok(ClientSeverity::Warning)
781 } else if s == ClientSeverity::Notice.as_str() {
782 Ok(ClientSeverity::Notice)
783 } else if s == ClientSeverity::Info.as_str() {
784 Ok(ClientSeverity::Info)
785 } else if s == ClientSeverity::Log.as_str() {
786 Ok(ClientSeverity::Log)
787 } else if s == ClientSeverity::Debug1.as_str() {
788 Ok(ClientSeverity::Debug1)
789 } else if s == ClientSeverity::Debug2.as_str() || s == "debug" {
791 Ok(ClientSeverity::Debug2)
792 } else if s == ClientSeverity::Debug3.as_str() {
793 Ok(ClientSeverity::Debug3)
794 } else if s == ClientSeverity::Debug4.as_str() {
795 Ok(ClientSeverity::Debug4)
796 } else if s == ClientSeverity::Debug5.as_str() {
797 Ok(ClientSeverity::Debug5)
798 } else {
799 Err(VarParseError::ConstrainedParameter {
800 invalid_values: input.to_vec(),
801 valid_values: Some(ClientSeverity::valid_values()),
802 })
803 }
804 }
805
806 fn box_clone(&self) -> Box<dyn Value> {
807 Box::new(self.clone())
808 }
809
810 fn format(&self) -> String {
811 self.as_str().into()
812 }
813}
814
815#[derive(Clone, Copy, Debug, Eq, PartialEq)]
820pub enum TimeZone {
821 UTC,
823 GMT,
825 FixedOffset(&'static str),
828}
829
830impl TimeZone {
831 fn as_str(&self) -> &'static str {
832 match self {
833 TimeZone::UTC => "UTC",
834 TimeZone::GMT => "GMT",
835 TimeZone::FixedOffset(s) => s,
836 }
837 }
838}
839
840impl Value for TimeZone {
841 fn type_name() -> Cow<'static, str>
842 where
843 Self: Sized,
844 {
845 "string".into()
847 }
848
849 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
850 where
851 Self: Sized,
852 {
853 let s = extract_single_value(input)?;
854 let s = UncasedStr::new(s);
855
856 if s == TimeZone::UTC.as_str() {
857 Ok(TimeZone::UTC)
858 } else if s == TimeZone::GMT.as_str() {
859 Ok(TimeZone::GMT)
860 } else if s == "+00:00" {
861 Ok(TimeZone::FixedOffset("+00:00"))
862 } else {
863 Err(VarParseError::ConstrainedParameter {
864 invalid_values: input.to_vec(),
865 valid_values: None,
866 })
867 }
868 }
869
870 fn box_clone(&self) -> Box<dyn Value> {
871 Box::new(self.clone())
872 }
873
874 fn format(&self) -> String {
875 self.as_str().into()
876 }
877}
878
879#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
881pub enum IsolationLevel {
882 ReadUncommitted,
883 ReadCommitted,
884 RepeatableRead,
885 Serializable,
886 StrongSessionSerializable,
924 StrictSerializable,
925 BoundedStaleness(std::time::Duration),
930}
931
932impl IsolationLevel {
933 const READ_UNCOMMITTED: &'static str = "read uncommitted";
934 const READ_COMMITTED: &'static str = "read committed";
935 const REPEATABLE_READ: &'static str = "repeatable read";
936 const SERIALIZABLE: &'static str = "serializable";
937 const STRONG_SESSION_SERIALIZABLE: &'static str = "strong session serializable";
938 const STRICT_SERIALIZABLE: &'static str = "strict serializable";
939 const BOUNDED_STALENESS: &'static str = "bounded staleness";
940 const BOUNDED_STALENESS_HINT: &'static str = "bounded staleness <duration>";
941
942 pub fn is_bounded_staleness(&self) -> bool {
944 matches!(self, Self::BoundedStaleness(_))
945 }
946
947 pub fn as_variant_str(&self) -> &'static str {
951 match self {
952 Self::ReadUncommitted => Self::READ_UNCOMMITTED,
953 Self::ReadCommitted => Self::READ_COMMITTED,
954 Self::RepeatableRead => Self::REPEATABLE_READ,
955 Self::Serializable => Self::SERIALIZABLE,
956 Self::StrongSessionSerializable => Self::STRONG_SESSION_SERIALIZABLE,
957 Self::StrictSerializable => Self::STRICT_SERIALIZABLE,
958 Self::BoundedStaleness(_) => Self::BOUNDED_STALENESS,
959 }
960 }
961
962 fn valid_values() -> Vec<&'static str> {
963 vec![
964 Self::READ_UNCOMMITTED,
965 Self::READ_COMMITTED,
966 Self::REPEATABLE_READ,
967 Self::SERIALIZABLE,
968 Self::STRICT_SERIALIZABLE,
970 Self::BOUNDED_STALENESS_HINT,
971 ]
972 }
973}
974
975impl fmt::Display for IsolationLevel {
976 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
977 match self {
978 Self::BoundedStaleness(d) => write!(
979 f,
980 "{} {}",
981 Self::BOUNDED_STALENESS,
982 humantime::format_duration(*d)
983 ),
984 other => f.write_str(other.as_variant_str()),
985 }
986 }
987}
988
989impl Value for IsolationLevel {
990 fn type_name() -> Cow<'static, str>
991 where
992 Self: Sized,
993 {
994 "string".into()
995 }
996
997 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
998 where
999 Self: Sized,
1000 {
1001 let invalid = || VarParseError::ConstrainedParameter {
1002 invalid_values: input.to_vec(),
1003 valid_values: Some(IsolationLevel::valid_values()),
1004 };
1005
1006 let s = extract_single_value(input)?;
1007 let lower = s.to_ascii_lowercase();
1008 let lower = lower.trim();
1009
1010 match lower {
1011 Self::READ_UNCOMMITTED
1013 | Self::READ_COMMITTED
1014 | Self::REPEATABLE_READ
1015 | Self::SERIALIZABLE => Ok(Self::Serializable),
1016 Self::STRONG_SESSION_SERIALIZABLE => Ok(Self::StrongSessionSerializable),
1017 Self::STRICT_SERIALIZABLE => Ok(Self::StrictSerializable),
1018 other => {
1019 let rest = other
1020 .strip_prefix(Self::BOUNDED_STALENESS)
1021 .ok_or_else(invalid)?;
1022 if !rest.starts_with(char::is_whitespace) {
1025 return Err(invalid());
1026 }
1027 let rest = rest.trim();
1028 if rest.is_empty() {
1029 return Err(invalid());
1030 }
1031 let d = humantime::parse_duration(rest).map_err(|_| invalid())?;
1032 if d < std::time::Duration::from_millis(1) {
1037 return Err(invalid());
1038 }
1039 Ok(Self::BoundedStaleness(d))
1040 }
1041 }
1042 }
1043
1044 fn box_clone(&self) -> Box<dyn Value> {
1045 Box::new(self.clone())
1046 }
1047
1048 fn format(&self) -> String {
1049 self.to_string()
1050 }
1051}
1052
1053impl From<TransactionIsolationLevel> for IsolationLevel {
1054 fn from(transaction_isolation_level: TransactionIsolationLevel) -> Self {
1055 match transaction_isolation_level {
1056 TransactionIsolationLevel::ReadUncommitted => Self::ReadUncommitted,
1057 TransactionIsolationLevel::ReadCommitted => Self::ReadCommitted,
1058 TransactionIsolationLevel::RepeatableRead => Self::RepeatableRead,
1059 TransactionIsolationLevel::Serializable => Self::Serializable,
1060 TransactionIsolationLevel::StrongSessionSerializable => Self::StrongSessionSerializable,
1061 TransactionIsolationLevel::StrictSerializable => Self::StrictSerializable,
1062 }
1063 }
1064}
1065
1066impl Value for CloneableEnvFilter {
1067 fn type_name() -> Cow<'static, str>
1068 where
1069 Self: Sized,
1070 {
1071 "EnvFilter".into()
1072 }
1073
1074 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
1075 where
1076 Self: Sized,
1077 {
1078 let s = extract_single_value(input)?;
1079 CloneableEnvFilter::from_str(s).map_err(|e| VarParseError::InvalidParameterValue {
1080 invalid_values: vec![s.to_string()],
1081 reason: e.to_string(),
1082 })
1083 }
1084
1085 fn box_clone(&self) -> Box<dyn Value> {
1086 Box::new(self.clone())
1087 }
1088
1089 fn format(&self) -> String {
1090 self.to_string()
1091 }
1092}
1093
1094#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1095pub enum ClientEncoding {
1096 Utf8,
1097}
1098
1099impl ClientEncoding {
1100 fn as_str(&self) -> &'static str {
1101 match self {
1102 ClientEncoding::Utf8 => "UTF8",
1103 }
1104 }
1105
1106 fn valid_values() -> Vec<&'static str> {
1107 vec![ClientEncoding::Utf8.as_str()]
1108 }
1109}
1110
1111impl Value for ClientEncoding {
1112 fn type_name() -> Cow<'static, str>
1113 where
1114 Self: Sized,
1115 {
1116 "string".into()
1117 }
1118
1119 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
1120 where
1121 Self: Sized,
1122 {
1123 let s = extract_single_value(input)?;
1124 let s = UncasedStr::new(s);
1125 if s == Self::Utf8.as_str() {
1126 Ok(Self::Utf8)
1127 } else {
1128 Err(VarParseError::ConstrainedParameter {
1129 invalid_values: vec![s.to_string()],
1130 valid_values: Some(ClientEncoding::valid_values()),
1131 })
1132 }
1133 }
1134
1135 fn box_clone(&self) -> Box<dyn Value> {
1136 Box::new(self.clone())
1137 }
1138
1139 fn format(&self) -> String {
1140 self.as_str().to_string()
1141 }
1142}
1143
1144#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1145pub enum IntervalStyle {
1146 Postgres,
1147}
1148
1149impl IntervalStyle {
1150 fn as_str(&self) -> &'static str {
1151 match self {
1152 IntervalStyle::Postgres => "postgres",
1153 }
1154 }
1155
1156 fn valid_values() -> Vec<&'static str> {
1157 vec![IntervalStyle::Postgres.as_str()]
1158 }
1159}
1160
1161impl Value for IntervalStyle {
1162 fn type_name() -> Cow<'static, str>
1163 where
1164 Self: Sized,
1165 {
1166 "string".into()
1167 }
1168
1169 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
1170 where
1171 Self: Sized,
1172 {
1173 let s = extract_single_value(input)?;
1174 let s = UncasedStr::new(s);
1175 if s == Self::Postgres.as_str() {
1176 Ok(Self::Postgres)
1177 } else {
1178 Err(VarParseError::ConstrainedParameter {
1179 invalid_values: vec![s.to_string()],
1180 valid_values: Some(IntervalStyle::valid_values()),
1181 })
1182 }
1183 }
1184
1185 fn box_clone(&self) -> Box<dyn Value> {
1186 Box::new(self.clone())
1187 }
1188
1189 fn format(&self) -> String {
1190 self.as_str().to_string()
1191 }
1192}
1193
1194macro_rules! impl_value_for_simple {
1200 ($t: ty, $name: literal) => {
1201 impl Value for $t {
1202 fn type_name() -> Cow<'static, str>
1203 where
1204 Self: Sized,
1205 {
1206 $name.into()
1207 }
1208
1209 fn parse(input: VarInput<'_>) -> Result<Self, VarParseError>
1210 where
1211 Self: Sized,
1212 {
1213 let s = extract_single_value(input)?;
1214 s.parse::<Self>()
1215 .map_err(|_| VarParseError::InvalidParameterType)
1216 }
1217
1218 fn box_clone(&self) -> Box<dyn Value> {
1219 Box::new(self.clone())
1220 }
1221
1222 fn format(&self) -> String {
1223 self.to_string()
1224 }
1225 }
1226 };
1227}
1228
1229impl_value_for_simple!(i32, "integer");
1230impl_value_for_simple!(u32, "unsigned integer");
1231impl_value_for_simple!(u64, "64-bit unsigned integer");
1232impl_value_for_simple!(usize, "unsigned integer");
1233impl_value_for_simple!(f64, "double-precision floating-point number");
1234
1235impl_value_for_simple!(NonZeroU32, "unsigned integer");
1236
1237impl_value_for_simple!(mz_repr::Timestamp, "mz-timestamp");
1238impl_value_for_simple!(mz_repr::bytes::ByteSize, "bytes");
1239impl_value_for_simple!(CompactionStyle, "rocksdb_compaction_style");
1240impl_value_for_simple!(CompressionType, "rocksdb_compression_type");
1241
1242#[cfg(test)]
1243mod tests {
1244 use mz_ore::assert_err;
1245
1246 use super::*;
1247
1248 #[mz_ore::test]
1249 fn test_value_duration() {
1250 fn inner(t: &'static str, e: Duration, expected_format: Option<&'static str>) {
1251 let d = Duration::parse(VarInput::Flat(t)).expect("invalid duration");
1252 assert_eq!(d, e);
1253 let mut d_format = d.format();
1254 d_format.retain(|c| !c.is_whitespace());
1255 if let Some(expected) = expected_format {
1256 assert_eq!(d_format, expected);
1257 } else {
1258 assert_eq!(
1259 t.chars().filter(|c| !c.is_whitespace()).collect::<String>(),
1260 d_format
1261 )
1262 }
1263 }
1264 inner("1", Duration::from_millis(1), Some("1ms"));
1265 inner("0", Duration::from_secs(0), Some("0s"));
1266 inner("1ms", Duration::from_millis(1), None);
1267 inner("1000ms", Duration::from_millis(1000), Some("1s"));
1268 inner("1001ms", Duration::from_millis(1001), None);
1269 inner("1us", Duration::from_micros(1), None);
1270 inner("1000us", Duration::from_micros(1000), Some("1ms"));
1271 inner("1s", Duration::from_secs(1), None);
1272 inner("60s", Duration::from_secs(60), Some("1min"));
1273 inner("3600s", Duration::from_secs(3600), Some("1h"));
1274 inner("3660s", Duration::from_secs(3660), Some("61min"));
1275 inner("1min", Duration::from_secs(1 * SEC_TO_MIN), None);
1276 inner("60min", Duration::from_secs(60 * SEC_TO_MIN), Some("1h"));
1277 inner("1h", Duration::from_secs(1 * SEC_TO_HOUR), None);
1278 inner("24h", Duration::from_secs(24 * SEC_TO_HOUR), Some("1d"));
1279 inner("1d", Duration::from_secs(1 * SEC_TO_DAY), None);
1280 inner("2d", Duration::from_secs(2 * SEC_TO_DAY), None);
1281 inner(" 1 s ", Duration::from_secs(1), None);
1282 inner("1s ", Duration::from_secs(1), None);
1283 inner(" 1s", Duration::from_secs(1), None);
1284 inner("0d", Duration::from_secs(0), Some("0s"));
1285 inner(
1286 "18446744073709551615",
1287 Duration::from_millis(u64::MAX),
1288 Some("18446744073709551615ms"),
1289 );
1290 inner(
1291 "18446744073709551615 s",
1292 Duration::from_secs(u64::MAX),
1293 Some("0"),
1294 );
1295
1296 fn errs(t: &'static str) {
1297 assert_err!(Duration::parse(VarInput::Flat(t)));
1298 }
1299 errs("1 m");
1300 errs("1 sec");
1301 errs("1 min 1 s");
1302 errs("1m1s");
1303 errs("1.1");
1304 errs("1.1 min");
1305 errs("-1 s");
1306 errs("");
1307 errs(" ");
1308 errs("x");
1309 errs("s");
1310 errs("18446744073709551615 min");
1311 errs("²");
1315 errs("1²ms");
1316 errs("½");
1317 errs("1ms");
1318 }
1319
1320 #[mz_ore::test]
1321 fn test_value_ident_list() {
1322 fn sql_set(values: &[&str]) -> Result<Vec<Ident>, VarParseError> {
1323 let values: Vec<String> = values.iter().map(|v| v.to_string()).collect();
1324 Vec::<Ident>::parse(VarInput::SqlSet(&values))
1325 }
1326 fn flat(value: &str) -> Result<Vec<Ident>, VarParseError> {
1327 Vec::<Ident>::parse(VarInput::Flat(value))
1328 }
1329
1330 for input in [&[""][..], &["a", "b"], &["A b", "c\"d"]] {
1332 let parsed = sql_set(input).expect("valid input");
1333 let formatted = parsed.format();
1334 assert_eq!(
1335 flat(&formatted).ok(),
1336 Some(parsed),
1337 "{input:?} does not round-trip through {formatted:?}"
1338 );
1339 }
1340
1341 assert_eq!(sql_set(&[""]).ok(), Some(vec![]));
1342 assert_eq!(flat("''").ok(), Some(vec![]));
1343 assert_err!(sql_set(&["a", ""]));
1344 assert_err!(flat("a, ''"));
1345 }
1346
1347 #[mz_ore::test]
1348 fn test_should_output_to_client() {
1349 #[rustfmt::skip]
1350 let test_cases = [
1351 (ClientSeverity::Debug1, vec![Severity::Debug, Severity::Log, Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1352 (ClientSeverity::Debug2, vec![Severity::Debug, Severity::Log, Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1353 (ClientSeverity::Debug3, vec![Severity::Debug, Severity::Log, Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1354 (ClientSeverity::Debug4, vec![Severity::Debug, Severity::Log, Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1355 (ClientSeverity::Debug5, vec![Severity::Debug, Severity::Log, Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1356 (ClientSeverity::Log, vec![Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1357 (ClientSeverity::Log, vec![Severity::Debug], false),
1358 (ClientSeverity::Info, vec![Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1359 (ClientSeverity::Info, vec![Severity::Debug, Severity::Log], false),
1360 (ClientSeverity::Notice, vec![Severity::Notice, Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1361 (ClientSeverity::Notice, vec![Severity::Debug, Severity::Log], false),
1362 (ClientSeverity::Warning, vec![Severity::Warning, Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1363 (ClientSeverity::Warning, vec![Severity::Debug, Severity::Log, Severity::Notice], false),
1364 (ClientSeverity::Error, vec![Severity::Error, Severity::Fatal, Severity:: Panic, Severity::Info], true),
1365 (ClientSeverity::Error, vec![Severity::Debug, Severity::Log, Severity::Notice, Severity::Warning], false),
1366 ];
1367
1368 for test_case in test_cases {
1369 run_test(test_case)
1370 }
1371
1372 fn run_test(test_case: (ClientSeverity, Vec<Severity>, bool)) {
1373 let client_min_messages_setting = test_case.0;
1374 let expected = test_case.2;
1375 for message_severity in test_case.1 {
1376 assert!(
1377 client_min_messages_setting.should_output_to_client(&message_severity)
1378 == expected
1379 )
1380 }
1381 }
1382 }
1383}
1384
1385#[cfg(test)]
1386mod bounded_staleness_tests {
1387 use super::*;
1388 use std::time::Duration;
1389
1390 fn parse_iso(s: &str) -> Result<IsolationLevel, VarParseError> {
1391 IsolationLevel::parse(VarInput::Flat(s))
1392 }
1393
1394 #[mz_ore::test]
1395 fn parses_bounded_staleness() {
1396 assert_eq!(
1397 parse_iso("bounded staleness 5s").unwrap(),
1398 IsolationLevel::BoundedStaleness(Duration::from_secs(5))
1399 );
1400 assert_eq!(
1401 parse_iso("bounded staleness 500ms").unwrap(),
1402 IsolationLevel::BoundedStaleness(Duration::from_millis(500))
1403 );
1404 assert_eq!(
1406 parse_iso("BOUNDED STALENESS 5s").unwrap(),
1407 IsolationLevel::BoundedStaleness(Duration::from_secs(5))
1408 );
1409 assert_eq!(
1411 parse_iso(" bounded staleness 5s ").unwrap(),
1412 IsolationLevel::BoundedStaleness(Duration::from_secs(5))
1413 );
1414 }
1415
1416 #[mz_ore::test]
1417 fn rejects_zero_duration() {
1418 assert!(parse_iso("bounded staleness 0s").is_err());
1419 assert!(parse_iso("bounded staleness 0ms").is_err());
1420 }
1421
1422 #[mz_ore::test]
1423 fn rejects_sub_millisecond_duration() {
1424 assert!(parse_iso("bounded staleness 1us").is_err());
1427 assert!(parse_iso("bounded staleness 999us").is_err());
1428 assert!(parse_iso("bounded staleness 999ns").is_err());
1429 }
1430
1431 #[mz_ore::test]
1432 fn parses_multi_component_duration() {
1433 let lvl = parse_iso("bounded staleness 1m30s").unwrap();
1436 assert_eq!(
1437 lvl,
1438 IsolationLevel::BoundedStaleness(Duration::from_secs(90))
1439 );
1440 let formatted = lvl.format();
1441 assert_eq!(parse_iso(&formatted).unwrap(), lvl);
1442 }
1443
1444 #[mz_ore::test]
1445 fn rejects_unparseable_duration() {
1446 assert!(parse_iso("bounded staleness banana").is_err());
1447 assert!(parse_iso("bounded staleness").is_err());
1448 assert!(parse_iso("bounded staleness5s").is_err());
1450 }
1451
1452 #[mz_ore::test]
1453 fn round_trips_format() {
1454 let lvl = IsolationLevel::BoundedStaleness(Duration::from_secs(5));
1455 assert_eq!(lvl.format(), "bounded staleness 5s");
1456 let parsed = parse_iso(&lvl.format()).unwrap();
1457 assert_eq!(parsed, lvl);
1458 }
1459
1460 #[mz_ore::test]
1461 fn accepts_long_durations() {
1462 assert!(parse_iso("bounded staleness 1h").is_ok());
1465 assert!(parse_iso("bounded staleness 24h").is_ok());
1466 }
1467}