libikarus 0.1.14

The core functionality of Ikarus wrapped neatly in a rust library
Documentation
use crate::objects::object::ObjectType;
use sqrite::sql_interface::{FromSql, FromSqlError, ToSql, ToSqlError};
use sqrite::value::Value;
use std::fmt::{Display, Formatter};

/// Ids come in two flavours.
/// The ones stored in the db, which are auto-generated by SQLite but don't contain any type information
/// And typed ids which contain type information. They are generated using a computed column that combines
/// the autogenerated id and the type information that is also stored in the db.
/// Previous approaches had us generate the id ourselves, but this ended up introducing a lot of complexity
/// since a static atomic counter could not have worked with multiple processes accessing the db simultaneously.
/// We solved this by introducing a connection id, which was defaulted to the process id. This connection ID was allotted 24 bits.
/// That worked well since PIDs are almost never above 24 bits.
/// windows is the only platform where this could occur, but it could be detected at connection-time.
/// With this new approach we still get the benefit of retrieving type information from the db.
/// Without having the drawback of id columns being > 64 bits,
/// which is a major storage-related concern since we store a lot of ID references in the db.
///
/// TypedIds are laid out in 2 sections:
/// X000 0000 - ignored sign bit + object type
/// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 - db-generated bits
/// We statically check that no TypedId can be generated that has the first 8 bit unset.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct TypedId(i64);

impl FromSql for TypedId {
    fn from_sql(value: Value) -> Result<Self, FromSqlError> {
        let value: i64 = FromSql::from_sql(value)?;

        if value & (0x7F << 56) == 0 {
            return Err(FromSqlError::Custom(String::from(
                "typed id is missing type information",
            )));
        }

        Ok(TypedId(value))
    }
}

impl Display for TypedId {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl TypedId {
    pub fn is_null(&self) -> bool {
        self.0 == 0
    }

    pub fn get_object_type(&self) -> ObjectType {
        let v = (self.0 >> 56) as i8;

        // no `unwrap_unchecked` because we don't need the performance gain, and technically
        // the id might have manually been updated in the database by someone, so a panic with some information
        // is more useful than a panic without any information
        ObjectType::from_int(v).expect(&format!("TypedId has invalid type: {}", v))
    }
}

impl ToSql for TypedId {
    fn to_sql(&self) -> Result<Value, ToSqlError> {
        Ok(Value::Integer(self.0))
    }
}