quill-sql 0.3.1

An educational Rust relational database (RDBMS) inspired by CMU 15445
Documentation
use crate::storage::record::TupleMeta;
use crate::transaction::{CommandId, TransactionId, INVALID_COMMAND_ID};
use serde::Serialize;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum TransactionStatus {
    InProgress,
    Committed,
    Aborted,
    Unknown,
}

impl TransactionStatus {
    fn is_committed(self) -> bool {
        matches!(self, TransactionStatus::Committed)
    }

    fn is_aborted(self) -> bool {
        matches!(self, TransactionStatus::Aborted)
    }
}

#[derive(Debug, Clone)]
pub struct TransactionSnapshot {
    txn_id: TransactionId,
    xmin: TransactionId,
    xmax: TransactionId,
    active_txns: Vec<TransactionId>,
}

impl TransactionSnapshot {
    pub fn new(
        txn_id: TransactionId,
        xmin: TransactionId,
        xmax: TransactionId,
        mut active_txns: Vec<TransactionId>,
    ) -> Self {
        active_txns.sort_unstable();
        Self {
            txn_id,
            xmin,
            xmax,
            active_txns,
        }
    }

    pub fn txn_id(&self) -> TransactionId {
        self.txn_id
    }

    pub fn xmin(&self) -> TransactionId {
        self.xmin
    }

    pub fn xmax(&self) -> TransactionId {
        self.xmax
    }

    pub fn active_txns(&self) -> &[TransactionId] {
        &self.active_txns
    }

    fn active_contains(&self, txn_id: TransactionId) -> bool {
        self.active_txns.binary_search(&txn_id).is_ok()
    }

    pub fn is_visible<F>(&self, meta: &TupleMeta, command_id: CommandId, mut status_of: F) -> bool
    where
        F: FnMut(TransactionId) -> TransactionStatus,
    {
        let inserter = meta.insert_txn_id;
        if inserter == 0 {
            return self.other_txn_delete_invisible(meta, command_id, &mut status_of);
        }

        if inserter == self.txn_id {
            if meta.insert_cid > command_id {
                return false;
            }
            return self.visible_after_local_delete(meta, command_id, &mut status_of);
        }

        if !self.other_txn_insert_visible(inserter, &mut status_of) {
            return false;
        }

        if !meta.is_deleted {
            return true;
        }

        self.other_txn_delete_invisible(meta, command_id, &mut status_of)
    }

    fn visible_after_local_delete<F>(
        &self,
        meta: &TupleMeta,
        command_id: CommandId,
        status_of: &mut F,
    ) -> bool
    where
        F: FnMut(TransactionId) -> TransactionStatus,
    {
        if !meta.is_deleted {
            return true;
        }
        let deleter = meta.delete_txn_id;
        if deleter == self.txn_id {
            if meta.delete_cid == INVALID_COMMAND_ID {
                return true;
            }
            return meta.delete_cid > command_id;
        }
        self.other_txn_delete_invisible(meta, command_id, status_of)
    }

    fn other_txn_insert_visible<F>(&self, inserter: TransactionId, status_of: &mut F) -> bool
    where
        F: FnMut(TransactionId) -> TransactionStatus,
    {
        if inserter >= self.xmax {
            return false;
        }

        let status = status_of(inserter);
        if status.is_aborted() {
            return false;
        }

        if inserter < self.xmin {
            return status.is_committed() || matches!(status, TransactionStatus::Unknown);
        }

        if self.active_contains(inserter) {
            return false;
        }

        matches!(
            status,
            TransactionStatus::Committed | TransactionStatus::Unknown
        )
    }

    fn other_txn_delete_invisible<F>(
        &self,
        meta: &TupleMeta,
        command_id: CommandId,
        status_of: &mut F,
    ) -> bool
    where
        F: FnMut(TransactionId) -> TransactionStatus,
    {
        if !meta.is_deleted {
            return true;
        }

        let deleter = meta.delete_txn_id;
        if deleter == 0 {
            return false;
        }

        if deleter == self.txn_id {
            if meta.delete_cid == INVALID_COMMAND_ID {
                return true;
            }
            return meta.delete_cid > command_id;
        }

        if deleter >= self.xmax {
            return true;
        }

        let status = status_of(deleter);
        if deleter < self.xmin {
            return !status.is_committed();
        }

        if self.active_contains(deleter) {
            return true;
        }

        match status {
            TransactionStatus::Committed => false,
            TransactionStatus::Aborted => true,
            TransactionStatus::InProgress => true,
            TransactionStatus::Unknown => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::record::TupleMeta;

    #[test]
    fn snapshot_filters_uncommitted() {
        let snapshot = TransactionSnapshot::new(2, 1, 5, vec![1, 3]);
        let meta = TupleMeta {
            insert_txn_id: 1,
            insert_cid: 0,
            delete_txn_id: 0,
            delete_cid: INVALID_COMMAND_ID,
            is_deleted: false,
            next_version: None,
            prev_version: None,
        };
        assert!(!snapshot.is_visible(&meta, 0, |_| TransactionStatus::InProgress));
    }

    #[test]
    fn snapshot_allows_committed_visible() {
        let snapshot = TransactionSnapshot::new(3, 2, 10, vec![4, 6]);
        let meta = TupleMeta {
            insert_txn_id: 1,
            insert_cid: 0,
            delete_txn_id: 0,
            delete_cid: INVALID_COMMAND_ID,
            is_deleted: false,
            next_version: None,
            prev_version: None,
        };
        assert!(snapshot.is_visible(&meta, 0, |_| TransactionStatus::Committed));
    }

    #[test]
    fn snapshot_hides_committed_delete() {
        let snapshot = TransactionSnapshot::new(3, 2, 10, vec![4, 6]);
        let meta = TupleMeta {
            insert_txn_id: 1,
            insert_cid: 0,
            delete_txn_id: 5,
            delete_cid: 0,
            is_deleted: true,
            next_version: None,
            prev_version: None,
        };
        assert!(!snapshot.is_visible(&meta, 0, |_| TransactionStatus::Committed));
    }

    #[test]
    fn snapshot_hides_system_deleted_tuple() {
        let snapshot = TransactionSnapshot::new(3, 2, 10, vec![4, 6]);
        let meta = TupleMeta {
            insert_txn_id: 0,
            insert_cid: 0,
            delete_txn_id: 0,
            delete_cid: 0,
            is_deleted: true,
            next_version: None,
            prev_version: None,
        };
        assert!(!snapshot.is_visible(&meta, 0, |_| TransactionStatus::Unknown));
    }

    #[test]
    fn snapshot_retains_tuple_when_delete_aborted() {
        let snapshot = TransactionSnapshot::new(3, 2, 10, vec![4, 6]);
        let meta = TupleMeta {
            insert_txn_id: 1,
            insert_cid: 0,
            delete_txn_id: 5,
            delete_cid: 0,
            is_deleted: true,
            next_version: None,
            prev_version: None,
        };
        assert!(snapshot.is_visible(&meta, 0, |txn| {
            if txn == 5 {
                TransactionStatus::Aborted
            } else {
                TransactionStatus::Committed
            }
        }));
    }
}