selene-core 0.2.0

selene-core is the backend for Selene, a local-first music player
Documentation
use blake3::Hash;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use sled::Tree;

use crate::database::{
    self, CompareAndSwapTransaction, Createable, DatabaseError, Mergeable, Patchable,
    deserialize_from_ivec, serialize_to_ivec, sled_get_all_raw, sled_get_batch_raw, sled_get_raw,
    sled_upsert,
    transaction_args::{apply_cas_tx, db_transaction},
};

pub trait DatabaseEntry: Serialize + DeserializeOwned + for<'de> Deserialize<'de> + Clone {
    type Id: std::ops::Deref<Target = Hash> + Copy;
    /// The current version number of the struct, used for updating
    const VERSION_NUMBER: u32;

    fn tree() -> Tree;

    /// Checks if an item exists in the database as lightly as possible
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to read the entry at the `id`
    fn db_check(id: Self::Id) -> database::Result<bool> {
        Ok(Self::tree().contains_key(id.as_bytes())?)
    }

    fn db_upsert(&self) -> database::Result<()> {
        sled_upsert(&Self::tree(), self.id().as_bytes(), serialize_to_ivec(self))?;
        Ok(())
    }

    fn tx_insert(&self, cas_tx: &mut CompareAndSwapTransaction) -> database::Result<()> {
        let self_request = cas_tx.get_or_new_request(Self::tree());

        if self_request.tree().contains_key(self.id().as_bytes())? {
            return Err(DatabaseError::AlreadyInDatabase);
        }

        self_request.swaps.insert(
            *self.id().as_bytes(),
            database::CompareAndSwapValue {
                old: None,
                new: Some(serialize_to_ivec(self)),
            },
        );

        Ok(())
    }

    /// Creates a new [`Self`] in the database using [`Self::CreateArgs`][`Createable::CreateArgs`]
    fn db_create(args: <Self as Createable>::CreateArgs) -> database::Result<Self>
    where
        Self: Createable,
    {
        let mut cas_tx = CompareAndSwapTransaction::new();
        let returned = Self::create(&mut cas_tx, args)?;
        apply_cas_tx(cas_tx)?;
        Ok(returned)
    }

    /// Gets the entry of [`Self`] with the given `id`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve the entry
    fn db_get(id: Self::Id) -> database::Result<Option<Self>> {
        Ok(sled_get_raw(&Self::tree(), id.as_bytes())?.map(deserialize_from_ivec))
    }

    /// Gets all entries of [`Self`] in the database
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve any entry
    fn db_get_all() -> database::Result<Vec<Self>> {
        Ok(sled_get_all_raw(&Self::tree())?
            .into_iter()
            .map(deserialize_from_ivec)
            .collect())
    }

    /// Gets all entries of [`Self`] in the database with the matching `ids`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve any entry
    fn db_get_batch(ids: &[Self::Id]) -> database::Result<Vec<Self>> {
        let items = sled_get_batch_raw(&Self::tree(), ids.iter().map(|a| a.as_bytes()))?;

        Ok(items.into_iter().map(deserialize_from_ivec).collect())
    }

    /// Merges `self` into `into` in the database.
    ///
    /// All items that point to `self` will be relinked to `into` and `self` will be deleted from the database
    fn db_merge(&self, into: Self::Id) -> database::Result<()>
    where
        Self: Mergeable,
    {
        db_transaction(|cas_tx| self.tx_merge(into, cas_tx))?;
        Ok(())
    }

    /// Patches or inserts [`Self`] into the database
    ///
    /// Patching rules depend on how [`Self`] implements [`Patchable<Self>`]
    fn db_patch(&self) -> database::Result<()>
    where
        Self: Patchable<Self>,
    {
        loop {
            let old_raw = Self::tree().get(self.id().as_bytes())?;
            let old: Option<Self> = old_raw.clone().map(deserialize_from_ivec);

            let patched = if let Some(mut old) = old {
                old.patch(self.clone());
                serialize_to_ivec(&old)
            } else {
                serialize_to_ivec(self)
            };

            if let Ok(()) =
                Self::tree().compare_and_swap(self.id().as_bytes(), old_raw, Some(patched))?
            {
                break;
            }
        }

        Ok(())
    }

    fn tx_patch(&self, cas_tx: &mut CompareAndSwapTransaction) -> database::Result<()>
    where
        Self: Patchable<Self>,
    {
        let old_raw = Self::tree().get(self.id().as_bytes())?;
        let item: Option<Self> = old_raw.clone().map(deserialize_from_ivec);

        let patched = if let Some(mut item) = item {
            item.patch(self.clone());
            serialize_to_ivec(&item)
        } else {
            serialize_to_ivec(self)
        };

        let self_request = cas_tx.get_or_new_request(Self::tree());
        self_request.swaps.insert(
            *self.id().as_bytes(),
            database::CompareAndSwapValue {
                old: old_raw,
                new: Some(patched),
            },
        );

        Ok(())
    }

    fn id(&self) -> Self::Id;
}