reallyme_foundationdb_kit/fdb/tenant/
data.rs1use std::num::NonZeroUsize;
7use std::sync::atomic::{AtomicBool, Ordering};
8
9use foundationdb::{
10 RangeOption, Transaction,
11 future::{FdbSlice, FdbValues},
12 options::{MutationType, TransactionOption},
13};
14use thiserror::Error;
15
16const RESERVED_METADATA_START: &[u8] = b"__meta";
17const RESERVED_METADATA_END: &[u8] = b"__metb";
18const MAX_TENANT_KEY_BYTES: usize = 10_000;
19const MAX_TENANT_VALUE_BYTES: usize = 100_000;
20const MAX_RANGE_RESULTS: usize = 64;
21const MAX_RANGE_TARGET_BYTES: usize = 1_000_000;
22const MAX_TRANSACTION_SIZE_LIMIT: i32 = 10_000_000;
23const VERSIONSTAMP_BYTES: usize = 10;
24const VERSIONSTAMP_OFFSET_BYTES: usize = 4;
25const MIN_STABLE_KEY_PREFIX_BYTES: usize = 6;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
29pub enum TenantDataAccessErrorReason {
30 #[error("tenant data key is empty")]
32 EmptyKey,
33 #[error("tenant data key exceeds the size limit")]
35 KeyTooLong,
36 #[error("tenant data key is reserved")]
38 ReservedKey,
39 #[error("tenant data value exceeds the size limit")]
41 ValueTooLong,
42 #[error("tenant data range bounds are invalid")]
44 InvalidRange,
45 #[error("tenant data range overlaps reserved metadata")]
47 ReservedRange,
48 #[error("tenant data limit is invalid")]
50 InvalidRangeLimit,
51 #[error("tenant data mutation is prohibited")]
53 KeyChangingMutation,
54}
55
56#[derive(Clone, Copy)]
58pub struct TenantTransactionSizeLimit(i32);
59
60impl TenantTransactionSizeLimit {
61 pub fn new(bytes: i32) -> Result<Self, TenantDataAccessErrorReason> {
63 if !(1..=MAX_TRANSACTION_SIZE_LIMIT).contains(&bytes) {
64 return Err(TenantDataAccessErrorReason::InvalidRangeLimit);
65 }
66 Ok(Self(bytes))
67 }
68}
69
70#[derive(Clone, Copy)]
72pub struct TenantDataRangeTargetBytes(NonZeroUsize);
73
74impl TenantDataRangeTargetBytes {
75 pub fn new(bytes: usize) -> Result<Self, TenantDataAccessErrorReason> {
77 let bytes = NonZeroUsize::new(bytes)
78 .filter(|bytes| bytes.get() <= MAX_RANGE_TARGET_BYTES)
79 .ok_or(TenantDataAccessErrorReason::InvalidRangeLimit)?;
80 Ok(Self(bytes))
81 }
82}
83
84#[derive(Clone, Copy)]
86pub struct TenantDataKey<'a> {
87 bytes: &'a [u8],
88}
89
90impl<'a> TenantDataKey<'a> {
91 pub fn new(bytes: &'a [u8]) -> Result<Self, TenantDataAccessErrorReason> {
93 if bytes.is_empty() {
94 return Err(TenantDataAccessErrorReason::EmptyKey);
95 }
96 if bytes.len() > MAX_TENANT_KEY_BYTES {
97 return Err(TenantDataAccessErrorReason::KeyTooLong);
98 }
99 if (RESERVED_METADATA_START..RESERVED_METADATA_END).contains(&bytes) || bytes[0] == 0xff {
100 return Err(TenantDataAccessErrorReason::ReservedKey);
101 }
102 Ok(Self { bytes })
103 }
104
105 pub fn as_bytes(self) -> &'a [u8] {
107 self.bytes
108 }
109}
110
111pub struct TenantDataRange<'a> {
113 begin: TenantDataKey<'a>,
114 end: TenantDataKey<'a>,
115}
116
117impl<'a> TenantDataRange<'a> {
118 pub fn new(begin: &'a [u8], end: &'a [u8]) -> Result<Self, TenantDataAccessErrorReason> {
120 let begin = TenantDataKey::new(begin)?;
121 let end = TenantDataKey::new(end)?;
122 if begin.as_bytes() >= end.as_bytes() {
123 return Err(TenantDataAccessErrorReason::InvalidRange);
124 }
125 if begin.as_bytes() < RESERVED_METADATA_END && end.as_bytes() > RESERVED_METADATA_START {
126 return Err(TenantDataAccessErrorReason::ReservedRange);
127 }
128 Ok(Self { begin, end })
129 }
130
131 pub fn begin(&self) -> &[u8] {
133 self.begin.as_bytes()
134 }
135
136 pub fn end(&self) -> &[u8] {
138 self.end.as_bytes()
139 }
140}
141
142#[derive(Clone, Copy)]
144pub struct TenantDataRangeLimit(NonZeroUsize);
145
146impl TenantDataRangeLimit {
147 pub const MAX: usize = MAX_RANGE_RESULTS;
149
150 pub fn new(value: usize) -> Result<Self, TenantDataAccessErrorReason> {
152 let value = NonZeroUsize::new(value)
153 .filter(|value| value.get() <= MAX_RANGE_RESULTS)
154 .ok_or(TenantDataAccessErrorReason::InvalidRangeLimit)?;
155 Ok(Self(value))
156 }
157
158 pub fn get(self) -> usize {
160 self.0.get()
161 }
162}
163
164pub struct TenantDataTransaction<'a> {
170 inner: &'a Transaction,
171 writable: bool,
172 mutation_rejected: AtomicBool,
173}
174
175impl<'a> TenantDataTransaction<'a> {
176 pub(super) fn new(inner: &'a Transaction, writable: bool) -> Self {
177 Self {
178 inner,
179 writable,
180 mutation_rejected: AtomicBool::new(false),
181 }
182 }
183
184 pub(super) fn mutation_rejected(&self) -> bool {
185 self.mutation_rejected.load(Ordering::Relaxed)
186 }
187
188 fn require_write(&self) -> Result<(), TenantDataAccessErrorReason> {
189 if self.writable {
190 Ok(())
191 } else {
192 self.mutation_rejected.store(true, Ordering::Relaxed);
195 Err(TenantDataAccessErrorReason::KeyChangingMutation)
196 }
197 }
198
199 pub fn set_size_limit(&self, limit: TenantTransactionSizeLimit) -> foundationdb::FdbResult<()> {
201 self.inner.set_option(TransactionOption::SizeLimit(limit.0))
202 }
203
204 pub async fn get(
206 &self,
207 key: TenantDataKey<'_>,
208 snapshot: bool,
209 ) -> foundationdb::FdbResult<Option<FdbSlice>> {
210 self.inner.get(key.as_bytes(), snapshot).await
211 }
212
213 pub fn set(
215 &self,
216 key: TenantDataKey<'_>,
217 value: &[u8],
218 ) -> Result<(), TenantDataAccessErrorReason> {
219 self.require_write()?;
220 if value.len() > MAX_TENANT_VALUE_BYTES {
221 return Err(TenantDataAccessErrorReason::ValueTooLong);
222 }
223 self.inner.set(key.as_bytes(), value);
224 Ok(())
225 }
226
227 pub fn clear(&self, key: TenantDataKey<'_>) {
230 let _ = self.try_clear(key);
231 }
232
233 pub fn try_clear(&self, key: TenantDataKey<'_>) -> Result<(), TenantDataAccessErrorReason> {
235 self.require_write()?;
236 self.inner.clear(key.as_bytes());
237 Ok(())
238 }
239
240 pub fn clear_range(&self, range: &TenantDataRange<'_>) {
243 let _ = self.try_clear_range(range);
244 }
245
246 pub fn try_clear_range(
248 &self,
249 range: &TenantDataRange<'_>,
250 ) -> Result<(), TenantDataAccessErrorReason> {
251 self.require_write()?;
252 self.inner.clear_range(range.begin(), range.end());
253 Ok(())
254 }
255
256 pub fn atomic_op(
258 &self,
259 key: TenantDataKey<'_>,
260 parameter: &[u8],
261 mutation: MutationType,
262 ) -> Result<(), TenantDataAccessErrorReason> {
263 self.require_write()?;
264 validate_atomic_mutation(mutation)?;
265 if parameter.len() > MAX_TENANT_VALUE_BYTES {
266 return Err(TenantDataAccessErrorReason::ValueTooLong);
267 }
268 self.inner.atomic_op(key.as_bytes(), parameter, mutation);
269 Ok(())
270 }
271
272 pub fn set_versionstamped_key(
277 &self,
278 key_template: &[u8],
279 value: &[u8],
280 ) -> Result<(), TenantDataAccessErrorReason> {
281 self.require_write()?;
282 validate_versionstamped_key_template(key_template)?;
283 if value.len() > MAX_TENANT_VALUE_BYTES {
284 return Err(TenantDataAccessErrorReason::ValueTooLong);
285 }
286 self.inner
287 .atomic_op(key_template, value, MutationType::SetVersionstampedKey);
288 Ok(())
289 }
290
291 pub async fn get_range(
293 &self,
294 range: &TenantDataRange<'_>,
295 limit: TenantDataRangeLimit,
296 snapshot: bool,
297 ) -> foundationdb::FdbResult<FdbValues> {
298 self.get_range_with_target_bytes(range, limit, MAX_RANGE_TARGET_BYTES, snapshot)
299 .await
300 }
301
302 pub async fn get_range_with_target(
304 &self,
305 range: &TenantDataRange<'_>,
306 limit: TenantDataRangeLimit,
307 target_bytes: TenantDataRangeTargetBytes,
308 snapshot: bool,
309 ) -> foundationdb::FdbResult<FdbValues> {
310 self.get_range_with_target_bytes(range, limit, target_bytes.0.get(), snapshot)
311 .await
312 }
313
314 async fn get_range_with_target_bytes(
315 &self,
316 range: &TenantDataRange<'_>,
317 limit: TenantDataRangeLimit,
318 target_bytes: usize,
319 snapshot: bool,
320 ) -> foundationdb::FdbResult<FdbValues> {
321 let mut options = RangeOption::from((range.begin(), range.end()));
322 options.limit = Some(limit.get());
323 options.target_bytes = target_bytes;
324 self.inner.get_range(&options, 1, snapshot).await
325 }
326}
327
328fn validate_versionstamped_key_template(
329 key_template: &[u8],
330) -> Result<(), TenantDataAccessErrorReason> {
331 let key_bytes = key_template
332 .len()
333 .checked_sub(VERSIONSTAMP_OFFSET_BYTES)
334 .ok_or(TenantDataAccessErrorReason::KeyChangingMutation)?;
335 TenantDataKey::new(&key_template[..key_bytes])?;
336 let offset_bytes: [u8; VERSIONSTAMP_OFFSET_BYTES] = key_template[key_bytes..]
337 .try_into()
338 .map_err(|_| TenantDataAccessErrorReason::KeyChangingMutation)?;
339 let offset = usize::try_from(u32::from_le_bytes(offset_bytes))
340 .map_err(|_| TenantDataAccessErrorReason::KeyChangingMutation)?;
341 let end = offset
342 .checked_add(VERSIONSTAMP_BYTES)
343 .ok_or(TenantDataAccessErrorReason::KeyChangingMutation)?;
344 if offset < MIN_STABLE_KEY_PREFIX_BYTES
345 || end > key_bytes
346 || key_template[offset..end].iter().any(|byte| *byte != 0xff)
347 {
348 return Err(TenantDataAccessErrorReason::KeyChangingMutation);
349 }
350 Ok(())
351}
352
353fn validate_atomic_mutation(mutation: MutationType) -> Result<(), TenantDataAccessErrorReason> {
354 match mutation {
357 MutationType::Add
358 | MutationType::And
359 | MutationType::BitAnd
360 | MutationType::Or
361 | MutationType::BitOr
362 | MutationType::Xor
363 | MutationType::BitXor
364 | MutationType::AppendIfFits
365 | MutationType::Max
366 | MutationType::Min
367 | MutationType::SetVersionstampedValue
368 | MutationType::ByteMin
369 | MutationType::ByteMax
370 | MutationType::CompareAndClear => Ok(()),
371 MutationType::SetVersionstampedKey => Err(TenantDataAccessErrorReason::KeyChangingMutation),
372 _ => Err(TenantDataAccessErrorReason::KeyChangingMutation),
373 }
374}
375
376#[cfg(test)]
377#[path = "data_tests.rs"]
378mod tests;