nodedb_sql/ddl_ast/parse/database/
quota_spec.rs1use nodedb_types::{PriorityClass, QuotaSpec};
9
10use crate::error::SqlError;
11
12pub fn parse_quota_spec(sql: &str, context: &str) -> Result<QuotaSpec, SqlError> {
17 let paren_start = sql.find('(').ok_or_else(|| SqlError::Parse {
19 detail: format!("{context}: expected '(' before quota arguments"),
20 })?;
21 let after = &sql[paren_start + 1..];
22 let paren_end = after.find(')').ok_or_else(|| SqlError::Parse {
23 detail: format!("{context}: unterminated '(' in quota clause"),
24 })?;
25 let inner = &after[..paren_end];
26
27 let mut spec = QuotaSpec::default();
28
29 for pair in inner.split(',') {
30 let pair = pair.trim();
31 if pair.is_empty() {
32 continue;
33 }
34 if pair.contains("=>") {
36 return Err(SqlError::Parse {
37 detail: format!(
38 "{context}: use '=' not '=>' for quota key-value pairs (near '{pair}')"
39 ),
40 });
41 }
42 let mut it = pair.splitn(2, '=');
43 let key = it.next().unwrap_or("").trim().to_lowercase();
44 let val = it
45 .next()
46 .ok_or_else(|| SqlError::Parse {
47 detail: format!("{context}: expected '=' in quota pair '{pair}'"),
48 })?
49 .trim()
50 .trim_matches('\'')
51 .trim_matches('"');
52
53 match key.as_str() {
54 "max_memory_bytes" => {
55 spec.max_memory_bytes = Some(val.parse::<u64>().map_err(|_| SqlError::Parse {
56 detail: format!(
57 "{context}: max_memory_bytes must be a non-negative integer, got '{val}'"
58 ),
59 })?);
60 }
61 "max_storage_bytes" => {
62 spec.max_storage_bytes = Some(val.parse::<u64>().map_err(|_| SqlError::Parse {
63 detail: format!(
64 "{context}: max_storage_bytes must be a non-negative integer, got '{val}'"
65 ),
66 })?);
67 }
68 "max_qps" => {
69 spec.max_qps = Some(val.parse::<u32>().map_err(|_| SqlError::Parse {
70 detail: format!(
71 "{context}: max_qps must be a non-negative integer, got '{val}'"
72 ),
73 })?);
74 }
75 "max_connections" => {
76 spec.max_connections = Some(val.parse::<u32>().map_err(|_| SqlError::Parse {
77 detail: format!(
78 "{context}: max_connections must be a non-negative integer, got '{val}'"
79 ),
80 })?);
81 }
82 "cache_weight" => {
83 let w = val.parse::<u32>().map_err(|_| SqlError::Parse {
84 detail: format!(
85 "{context}: cache_weight must be a positive integer, got '{val}'"
86 ),
87 })?;
88 if w == 0 {
89 return Err(SqlError::Parse {
90 detail: format!(
91 "{context}: cache_weight must be ≥ 1 (zero would mean \
92 no doc-cache capacity at all)"
93 ),
94 });
95 }
96 spec.cache_weight = Some(w);
97 }
98 "priority_class" => {
99 let pc = val.parse::<PriorityClass>().map_err(|e| SqlError::Parse {
100 detail: format!("{context}: invalid priority_class — {e}"),
101 })?;
102 spec.priority_class = Some(pc);
103 }
104 "maintenance_cpu_pct" => {
105 let pct = val.parse::<u8>().map_err(|_| SqlError::Parse {
106 detail: format!("{context}: maintenance_cpu_pct must be 0–100, got '{val}'"),
107 })?;
108 if pct > 100 {
109 return Err(SqlError::Parse {
110 detail: format!("{context}: maintenance_cpu_pct must be ≤ 100, got {pct}"),
111 });
112 }
113 spec.maintenance_cpu_pct = Some(pct);
114 }
115 other => {
116 return Err(SqlError::Parse {
117 detail: format!(
118 "{context}: unknown quota field '{other}'. \
119 Valid fields: max_memory_bytes, max_storage_bytes, max_qps, \
120 max_connections, cache_weight, priority_class, maintenance_cpu_pct"
121 ),
122 });
123 }
124 }
125 }
126
127 Ok(spec)
128}