surrealdb_core/sql/
bytesize.rs

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