Skip to content

Redbit derives code necessary for persisting and querying structured data into/from redb

License

Notifications You must be signed in to change notification settings

pragmaxim-com/redbit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redbit reads struct annotations and derives code necessary for persisting and querying structured data into/from redb using secondary indexes and dictionaries, let's say we want to persist Utxo into Redb using Redbit :

Main motivations

  • âś… Achieving more advanced querying capabilities with embedded KV stores is non-trivial
  • âś… Absence of any existing abstraction layer for structured data
  • âś… Handwriting custom codecs on byte-level is tedious and painful

Major Out-of-the-Box Features

  • âś… Querying and ranging by secondary index
  • âś… Optional dictionaries for low cardinality fields
  • âś… One-to-One and One-to-Many entities with cascade read/write/delete
  • âś… All goodies including intuitive data ordering without writing custom codecs

Declare annotated Struct examples/utxo/src/lib.rs:

    mod data;
    
    pub use data::*;
    pub use redbit::*;
    
    use serde::{Deserialize, Serialize};
    use std::fmt::Debug;
    
    pub type Amount = u64;
    pub type Timestamp = u64;
    pub type Height = u32;
    pub type TxIndex = u16;
    pub type UtxoIndex = u16;
    pub type AssetIndex = u16;
    pub type Datum = String;
    pub type Address = String;
    pub type AssetName = String;
    pub type PolicyId = String;
    pub type Hash = String;
    
    #[derive(Entity, Debug, Clone, PartialEq, Eq)]
    pub struct Block {
        #[pk(range)]
        pub id: BlockPointer,
        #[one2one]
        pub header: BlockHeader,
        #[one2many]
        pub transactions: Vec<Transaction>,
    }
    
    #[derive(Entity, Debug, Clone, PartialEq, Eq)]
    pub struct BlockHeader {
        #[pk(range)]
        pub id: BlockPointer,
        #[column(index)]
        pub hash: Hash,
        #[column(index, range)]
        pub timestamp: Timestamp,
        #[column(index)]
        pub merkle_root: Hash,
        #[column]
        pub nonce: u32,
    }
    
    #[derive(Entity, Debug, Clone, PartialEq, Eq)]
    pub struct Transaction {
        #[pk(range)]
        pub id: TxPointer,
        #[column(index)]
        pub hash: Hash,
        #[one2many]
        pub utxos: Vec<Utxo>,
    }
    
    #[derive(Entity, Debug, Clone, PartialEq, Eq)]
    pub struct Utxo {
        #[pk(range)]
        pub id: UtxoPointer,
        #[column]
        pub amount: Amount,
        #[column(index)]
        pub datum: Datum,
        #[column(index, dictionary)]
        pub address: Address,
        #[one2many]
        pub assets: Vec<Asset>,
    }
    
    #[derive(Entity, Debug, Clone, PartialEq, Eq)]
    pub struct Asset {
        #[pk(range)]
        pub id: AssetPointer,
        #[column]
        pub amount: Amount,
        #[column(index, dictionary)]
        pub name: AssetName,
        #[column(index, dictionary)]
        pub policy_id: PolicyId,
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
    pub struct BlockPointer {
        pub height: Height,
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
    pub struct TxPointer {
        pub block_pointer: BlockPointer,
        pub tx_index: TxIndex,
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
    pub struct UtxoPointer {
        pub tx_pointer: TxPointer,
        pub utxo_index: UtxoIndex,
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
    pub struct AssetPointer {
        pub utxo_pointer: UtxoPointer,
        pub asset_index: AssetIndex,
    }
    
    impl PK<TxPointer> for BlockPointer {
        fn fk_range(&self) -> (TxPointer, TxPointer) {
            (TxPointer { block_pointer: self.clone(), tx_index: TxIndex::MIN }, TxPointer { block_pointer: self.next(), tx_index: TxIndex::MIN })
        }
    
        fn next(&self) -> Self {
            BlockPointer{ height: self.height + 1 }
        }
    }
    
    impl PK<UtxoPointer> for TxPointer {
        fn fk_range(&self) -> (UtxoPointer, UtxoPointer) {
            (UtxoPointer { tx_pointer: self.clone(), utxo_index: UtxoIndex::MIN }, UtxoPointer { tx_pointer: self.next(), utxo_index: UtxoIndex::MIN })
        }
    
        fn next(&self) -> Self {
            TxPointer { block_pointer: self.block_pointer.clone(), tx_index: self.tx_index+1 }
        }
    }
    
    impl PK<AssetPointer> for UtxoPointer {
        fn fk_range(&self) -> (AssetPointer, AssetPointer) {
            (AssetPointer { utxo_pointer: self.clone(), asset_index: AssetIndex::MIN }, AssetPointer { utxo_pointer: self.next(), asset_index: AssetIndex::MIN })
        }
    
        fn next(&self) -> Self {
            UtxoPointer { tx_pointer: self.tx_pointer.clone(), utxo_index: self.utxo_index+1 }
        }
    }

And R/W entire instances efficiently using indexes and dictionaries examples/utxo/src/main.rs:

    use utxo::*;
    
    fn demo() -> Result<(), DbEngineError> {
        let db = redb::Database::create(std::env::temp_dir().join("my_db.redb"))?;
        let blocks = get_blocks(10, 5, 5, 5);
    
        println!("Persisting blocks:");
        for block in blocks.iter() {
            Block::store_and_commit(&db, block)?
        }
    
        let read_tx = db.begin_read()?;
    
        println!("Querying blocks:");
        let first_block = Block::first(&read_tx)?.unwrap();
        let last_block = Block::last(&read_tx)?.unwrap();
    
        Block::all(&read_tx)?;
        Block::get(&read_tx, &first_block.id)?;
        Block::range(&read_tx, &first_block.id, &last_block.id)?;
        Block::get_transactions(&read_tx, &first_block.id)?;
        Block::get_header(&read_tx, &first_block.id)?;
    
        println!("Querying block headers:");
        let first_block_header = BlockHeader::first(&read_tx)?.unwrap();
        let last_block_header = BlockHeader::last(&read_tx)?.unwrap();
    
        BlockHeader::all(&read_tx)?;
        BlockHeader::get(&read_tx, &first_block_header.id)?;
        BlockHeader::range(&read_tx, &first_block_header.id, &last_block_header.id)?;
        BlockHeader::range_by_timestamp(&read_tx, &first_block_header.timestamp, &last_block_header.timestamp)?;
        BlockHeader::get_by_hash(&read_tx, &first_block_header.hash)?;
        BlockHeader::get_by_timestamp(&read_tx, &first_block_header.timestamp)?;
        BlockHeader::get_by_merkle_root(&read_tx, &first_block_header.merkle_root)?;
    
        println!("Querying transactions:");
        let first_transaction = Transaction::first(&read_tx)?.unwrap();
        let last_transaction = Transaction::last(&read_tx)?.unwrap();
    
        Transaction::all(&read_tx)?;
        Transaction::get(&read_tx, &first_transaction.id)?;
        Transaction::get_by_hash(&read_tx, &first_transaction.hash)?;
        Transaction::range(&read_tx, &first_transaction.id, &last_transaction.id)?;
        Transaction::get_utxos(&read_tx, &first_transaction.id)?;
    
        println!("Querying utxos:");
        let first_utxo = Utxo::first(&read_tx)?.unwrap();
        let last_utxo = Utxo::last(&read_tx)?.unwrap();
    
        Utxo::all(&read_tx)?;
        Utxo::get(&read_tx, &first_utxo.id)?;
        Utxo::get_by_address(&read_tx, &first_utxo.address)?;
        Utxo::get_by_datum(&read_tx, &first_utxo.datum)?;
        Utxo::range(&read_tx, &first_utxo.id, &last_utxo.id)?;
        Utxo::get_assets(&read_tx, &first_utxo.id)?;
    
        println!("Querying assets:");
        let first_asset = Asset::first(&read_tx)?.unwrap();
        let last_asset = Asset::last(&read_tx)?.unwrap();
    
        Asset::all(&read_tx)?;
        Asset::get(&read_tx, &first_asset.id)?;
        Asset::get_by_name(&read_tx, &first_asset.name)?;
        Asset::get_by_policy_id(&read_tx, &first_asset.policy_id)?;
        Asset::range(&read_tx, &first_asset.id, &last_asset.id)?;
    
        println!("Deleting blocks:");
        for block in blocks.iter() {
            Block::delete_and_commit(&db, &block.id)?
        }
        Ok(())
    }
    
    fn main() {
        demo().unwrap()
    }

About

Redbit derives code necessary for persisting and querying structured data into/from redb

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages