178 lines
4.2 KiB
Rust
178 lines
4.2 KiB
Rust
use crate::err::Error;
|
|
use crate::kvs::{Key, Val};
|
|
use roaring::RoaringBitmap;
|
|
use serde::de::DeserializeOwned;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// Id is a unique id generated by the generator.
|
|
pub(crate) type Id = u32;
|
|
|
|
// U64 is a generator that generates unique unsigned 64-bit integer ids.
|
|
// It can reuse freed ids by keeping track of them in a roaring bitmap.
|
|
// This doesn't do any variable-length encoding, so it's not as space efficient as it could be.
|
|
// It is used to generate ids for any SurrealDB objects that need aliases (e.g. namespaces, databases, tables, indexes, etc.)
|
|
#[derive(Clone)]
|
|
pub struct U32 {
|
|
state_key: Key,
|
|
available_ids: Option<RoaringBitmap>,
|
|
next_id: Id,
|
|
updated: bool,
|
|
}
|
|
|
|
impl U32 {
|
|
pub(crate) async fn new(state_key: Key, v: Option<Val>) -> Result<Self, Error> {
|
|
let state: State = if let Some(val) = v {
|
|
State::try_from_val(val)?
|
|
} else {
|
|
State::new()
|
|
};
|
|
Ok(Self {
|
|
state_key,
|
|
available_ids: state.available_ids,
|
|
updated: false,
|
|
next_id: state.next_id,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn get_next_id(&mut self) -> Id {
|
|
self.updated = true;
|
|
|
|
// We check first if there is any available id
|
|
if let Some(available_ids) = &mut self.available_ids {
|
|
if let Some(available_id) = available_ids.iter().next() {
|
|
available_ids.remove(available_id);
|
|
if available_ids.is_empty() {
|
|
self.available_ids = None;
|
|
}
|
|
return available_id;
|
|
}
|
|
}
|
|
// If not, we use the sequence
|
|
let doc_id = self.next_id;
|
|
self.next_id += 1;
|
|
doc_id
|
|
}
|
|
|
|
pub(crate) fn remove_id(&mut self, id: Id) {
|
|
if let Some(available_ids) = &mut self.available_ids {
|
|
available_ids.insert(id);
|
|
} else {
|
|
let mut available_ids = RoaringBitmap::new();
|
|
available_ids.insert(id);
|
|
self.available_ids = Some(available_ids);
|
|
}
|
|
self.updated = true;
|
|
}
|
|
|
|
pub(crate) fn finish(&mut self) -> Option<(Key, Val)> {
|
|
if self.updated {
|
|
let state = State {
|
|
available_ids: self.available_ids.take(),
|
|
next_id: self.next_id,
|
|
};
|
|
return Some((self.state_key.clone(), state.try_to_val().unwrap()));
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct State {
|
|
available_ids: Option<RoaringBitmap>,
|
|
next_id: Id,
|
|
}
|
|
|
|
impl State {
|
|
fn new() -> Self {
|
|
Self {
|
|
available_ids: None,
|
|
next_id: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) trait SerdeState
|
|
where
|
|
Self: Sized + Serialize + DeserializeOwned,
|
|
{
|
|
fn try_to_val(&self) -> Result<Val, Error> {
|
|
Ok(bincode::serialize(self)?)
|
|
}
|
|
|
|
fn try_from_val(val: Val) -> Result<Self, Error> {
|
|
Ok(bincode::deserialize(&val)?)
|
|
}
|
|
}
|
|
|
|
impl SerdeState for RoaringBitmap {}
|
|
impl SerdeState for State {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::err::Error;
|
|
use crate::idg::u32::U32;
|
|
use crate::kvs::{Datastore, LockType::*, Transaction, TransactionType::*};
|
|
|
|
async fn get_ids(ds: &Datastore) -> (Transaction, U32) {
|
|
let mut tx = ds.transaction(Write, Optimistic).await.unwrap();
|
|
let key = "foo";
|
|
let v = tx.get(key).await.unwrap();
|
|
let d = U32::new(key.into(), v).await.unwrap();
|
|
(tx, d)
|
|
}
|
|
|
|
async fn finish(mut tx: Transaction, mut d: U32) -> Result<(), Error> {
|
|
if let Some((key, val)) = d.finish() {
|
|
tx.set(key, val).await?;
|
|
}
|
|
tx.commit().await
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_remove_ids() {
|
|
let ds = Datastore::new("memory").await.unwrap();
|
|
|
|
// Get the first id
|
|
{
|
|
let (tx, mut d) = get_ids(&ds).await;
|
|
let id = d.get_next_id();
|
|
finish(tx, d).await.unwrap();
|
|
assert_eq!(id, 0);
|
|
}
|
|
|
|
// Get the second and the third ids
|
|
{
|
|
let (tx, mut d) = get_ids(&ds).await;
|
|
let id1 = d.get_next_id();
|
|
let id2 = d.get_next_id();
|
|
finish(tx, d).await.unwrap();
|
|
assert_eq!(id1, 1);
|
|
assert_eq!(id2, 2);
|
|
}
|
|
|
|
// It reuses the removed id within a transaction
|
|
{
|
|
let (tx, mut d) = get_ids(&ds).await;
|
|
d.remove_id(1);
|
|
let id1 = d.get_next_id();
|
|
let id2 = d.get_next_id();
|
|
finish(tx, d).await.unwrap();
|
|
assert_eq!(id1, 1);
|
|
assert_eq!(id2, 3);
|
|
}
|
|
|
|
// It reuses the removed id across transactions
|
|
{
|
|
let (tx, mut d1) = get_ids(&ds).await;
|
|
d1.remove_id(2);
|
|
finish(tx, d1).await.unwrap();
|
|
|
|
let (tx, mut d2) = get_ids(&ds).await;
|
|
let id1 = d2.get_next_id();
|
|
let id2 = d2.get_next_id();
|
|
finish(tx, d2).await.unwrap();
|
|
assert_eq!(id1, 2);
|
|
assert_eq!(id2, 4);
|
|
}
|
|
}
|
|
}
|