surrealdb_expr/expr/
bytesize.rs1use std::fmt::Display;
2use std::iter::Sum;
3use std::str::FromStr;
4use std::{fmt, ops};
5
6use anyhow::{Result, bail, ensure};
7use surrealdb_types::{SqlFormat, ToSql, write_sql};
8
9use crate::expr::Error;
10use crate::expr::statements::info::InfoStructure;
11use crate::val::{TryAdd, TrySub, Value};
12
13#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Hash, Ord)]
14pub struct Bytesize(pub u64);
15
16const KIB: u64 = 1024;
17const MIB: u64 = KIB * 1024;
18const GIB: u64 = MIB * 1024;
19const TIB: u64 = GIB * 1024;
20const PIB: u64 = TIB * 1024;
21
22impl FromStr for Bytesize {
23 type Err = anyhow::Error;
24 fn from_str(s: &str) -> Result<Self, Self::Err> {
25 Bytesize::parse(s)
26 }
27}
28
29impl Bytesize {
30 pub const ZERO: Bytesize = Bytesize(0);
31 pub const MAX: Bytesize = Bytesize(u64::MAX);
32
33 pub fn new(b: u64) -> Self {
34 Bytesize(b)
35 }
36
37 pub fn parse(input: &str) -> Result<Self> {
38 let input = input.trim();
39 ensure!(!input.is_empty(), Error::InvalidBytesize);
40
41 let mut chars = input.as_bytes().iter().peekable();
42 let mut total = Bytesize::new(0);
43
44 while chars.peek().is_some() {
45 let mut value = 0u64;
47 while let Some(&c) = chars.peek() {
48 if !c.is_ascii_digit() {
49 break;
50 }
51
52 chars.next();
53 value = value.checked_mul(10).ok_or(Error::InvalidBytesize)?;
54 value = value.checked_add((c - b'0') as u64).ok_or(Error::InvalidBytesize)?;
55 }
56
57 let unit = chars.next().ok_or(Error::InvalidBytesize)?.to_ascii_lowercase();
59
60 if unit != b'b' {
62 match chars.next().map(|x| x.to_ascii_lowercase()) {
63 Some(b'b') => (),
64 _ => bail!(Error::InvalidBytesize),
65 }
66 }
67
68 let bytesize = match unit {
69 b'b' => Bytesize::b(value),
70 b'k' => Bytesize::kb(value),
71 b'm' => Bytesize::mb(value),
72 b'g' => Bytesize::gb(value),
73 b't' => Bytesize::tb(value),
74 b'p' => Bytesize::pb(value),
75 _ => bail!(Error::InvalidBytesize),
76 };
77
78 total = total.try_add(bytesize)?;
79 }
80
81 ensure!(total != Bytesize::new(0), Error::InvalidBytesize);
82
83 Ok(total)
84 }
85
86 pub fn b(b: u64) -> Self {
87 Bytesize(b)
88 }
89
90 pub fn kb(kb: u64) -> Self {
91 Bytesize(kb * KIB)
92 }
93
94 pub fn mb(mb: u64) -> Self {
95 Bytesize(mb * MIB)
96 }
97
98 pub fn gb(gb: u64) -> Self {
99 Bytesize(gb * GIB)
100 }
101
102 pub fn tb(tb: u64) -> Self {
103 Bytesize(tb * TIB)
104 }
105
106 pub fn pb(pb: u64) -> Self {
107 Bytesize(pb * PIB)
108 }
109}
110
111impl ToSql for Bytesize {
112 fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
113 let b = self.0;
114 let pb = b / PIB;
115 let b = b % PIB;
116 let tb = b / TIB;
117 let b = b % TIB;
118 let gb = b / GIB;
119 let b = b % GIB;
120 let mb = b / MIB;
121 let b = b % MIB;
122 let kb = b / KIB;
123 let b = b % KIB;
124
125 if pb > 0 {
126 write_sql!(f, sql_fmt, "{pb}pb");
127 }
128 if tb > 0 {
129 write_sql!(f, sql_fmt, "{tb}tb");
130 }
131 if gb > 0 {
132 write_sql!(f, sql_fmt, "{gb}gb");
133 }
134 if mb > 0 {
135 write_sql!(f, sql_fmt, "{mb}mb");
136 }
137 if kb > 0 {
138 write_sql!(f, sql_fmt, "{kb}kb");
139 }
140 if b > 0 {
141 write_sql!(f, sql_fmt, "{b}b");
142 }
143 }
144}
145
146impl Display for Bytesize {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 write!(f, "{}", self.to_sql())
149 }
150}
151
152impl ops::Add for Bytesize {
153 type Output = Self;
154 fn add(self, other: Self) -> Self {
155 match self.0.checked_add(other.0) {
157 Some(v) => Bytesize::new(v),
158 None => Bytesize::new(u64::MAX),
159 }
160 }
161}
162
163impl TryAdd for Bytesize {
164 type Output = Self;
165 fn try_add(self, other: Self) -> Result<Self> {
166 self.0
167 .checked_add(other.0)
168 .ok_or_else(|| Error::ArithmeticOverflow(format!("{self} + {other}")))
169 .map_err(anyhow::Error::new)
170 .map(Bytesize::new)
171 }
172}
173
174impl<'b> ops::Add<&'b Bytesize> for &Bytesize {
175 type Output = Bytesize;
176 fn add(self, other: &'b Bytesize) -> Bytesize {
177 match self.0.checked_add(other.0) {
178 Some(v) => Bytesize::new(v),
179 None => Bytesize::new(u64::MAX),
180 }
181 }
182}
183
184impl<'b> TryAdd<&'b Bytesize> for &Bytesize {
185 type Output = Bytesize;
186 fn try_add(self, other: &'b Bytesize) -> Result<Bytesize> {
187 self.0
188 .checked_add(other.0)
189 .ok_or_else(|| Error::ArithmeticOverflow(format!("{self} + {other}")))
190 .map_err(anyhow::Error::new)
191 .map(Bytesize::new)
192 }
193}
194
195impl ops::Sub for Bytesize {
196 type Output = Self;
197 fn sub(self, other: Self) -> Self {
198 match self.0.checked_sub(other.0) {
199 Some(v) => Bytesize::new(v),
200 None => Bytesize::default(),
201 }
202 }
203}
204
205impl TrySub for Bytesize {
206 type Output = Self;
207 fn try_sub(self, other: Self) -> Result<Self> {
208 self.0
209 .checked_sub(other.0)
210 .ok_or_else(|| Error::ArithmeticNegativeOverflow(format!("{self} - {other}")))
211 .map_err(anyhow::Error::new)
212 .map(Bytesize::new)
213 }
214}
215
216impl<'b> ops::Sub<&'b Bytesize> for &Bytesize {
217 type Output = Bytesize;
218 fn sub(self, other: &'b Bytesize) -> Bytesize {
219 match self.0.checked_sub(other.0) {
220 Some(v) => Bytesize::new(v),
221 None => Bytesize::default(),
222 }
223 }
224}
225
226impl<'b> TrySub<&'b Bytesize> for &Bytesize {
227 type Output = Bytesize;
228 fn try_sub(self, other: &'b Bytesize) -> Result<Bytesize> {
229 self.0
230 .checked_sub(other.0)
231 .ok_or_else(|| Error::ArithmeticNegativeOverflow(format!("{self} - {other}")))
232 .map_err(anyhow::Error::new)
233 .map(Bytesize::new)
234 }
235}
236
237impl Sum<Self> for Bytesize {
238 fn sum<I>(iter: I) -> Bytesize
239 where
240 I: Iterator<Item = Self>,
241 {
242 iter.fold(Bytesize::default(), |a, b| a + b)
243 }
244}
245
246impl<'a> Sum<&'a Self> for Bytesize {
247 fn sum<I>(iter: I) -> Bytesize
248 where
249 I: Iterator<Item = &'a Self>,
250 {
251 iter.fold(Bytesize::default(), |a, b| &a + b)
252 }
253}
254
255impl InfoStructure for Bytesize {
256 fn structure(self) -> Value {
257 self.to_string().into()
258 }
259}
260
261#[cfg(test)]
262#[allow(clippy::unwrap_used)]
263mod tests {
264 use super::Bytesize;
265
266 #[test]
267 fn parse_bytesize() {
268 let str = "1tb8mb2b";
269 let bytesize = Bytesize::parse(str).unwrap();
270 assert_eq!(bytesize, Bytesize::new(1_099_520_016_386));
271 assert_eq!(str, bytesize.to_string());
272 }
273}