Skip to main content

surrealdb_expr/expr/
bytesize.rs

1use 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			// Parse number
46			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			// Parse unit
58			let unit = chars.next().ok_or(Error::InvalidBytesize)?.to_ascii_lowercase();
59
60			// Handle optional 'b' suffix
61			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		// checked to make sure it doesn't overflow
156		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}