Parse a `(field = value, ...)` clause from a raw SQL string into a [`QuotaSpec`]. Finds the first `(` after the `QUOTA` keyword, reads key=value pairs until `)`, and rejects unknown keys or `=>` used instead of `=`.
(sql: &str, context: &str)
| 14 | /// Finds the first `(` after the `QUOTA` keyword, reads key=value pairs until `)`, |
| 15 | /// and rejects unknown keys or `=>` used instead of `=`. |
| 16 | pub fn parse_quota_spec(sql: &str, context: &str) -> Result<QuotaSpec, SqlError> { |
| 17 | // Find the opening paren. |
| 18 | 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 | // Reject `=>` (fat arrow used in vector kwargs) — this is `=` only. |
| 35 | 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 | })?); |