/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin */ use crate::{query::Query, sort::Index}; use anyhow::{Result, anyhow}; use jellykv::{ReadTransaction, WriteTransaction}; use jellyobject::ObjectBuffer; pub type TableNum = u64; pub type RowNum = u64; pub struct Table { id: u32, pub(crate) indices: Vec>, } impl Table { pub fn new(id: u32) -> Self { Self { id, indices: Vec::new(), } } fn key(&self, row: RowNum) -> Vec { let mut key = Vec::new(); key.extend(self.id.to_be_bytes()); key.extend(row.to_be_bytes()); key } pub fn insert(&self, txn: &mut dyn WriteTransaction, entry: ObjectBuffer) -> Result { let mut id_counter = txn .get(&self.id.to_be_bytes())? .map(|k| k.as_slice().try_into().map(RowNum::from_be_bytes).ok()) .flatten() .unwrap_or(0); let row = id_counter; id_counter += 1; txn.set(&self.id.to_be_bytes(), &id_counter.to_be_bytes())?; txn.set(&self.key(row), bytemuck::cast_slice(entry.0.as_slice()))?; let ob = entry.as_object(); for idx in &self.indices { idx.add(txn, row, ob)?; } Ok(row) } pub fn add_index(&mut self, index: T) -> T { self.indices.push(Box::new(index.clone())); index } pub fn get(&self, txn: &dyn ReadTransaction, row: RowNum) -> Result> { Ok(txn.get(&self.key(row))?.map(ObjectBuffer::from)) } pub fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum) -> Result { let Some(entry) = self.get(db, row)? else { return Ok(false); }; let ob = entry.as_object(); for idx in &self.indices { idx.remove(db, row, ob)?; } db.del(&self.key(row))?; Ok(true) } pub fn update( &self, txn: &mut dyn WriteTransaction, row: RowNum, entry: ObjectBuffer, ) -> Result<()> { let before = self .get(txn, row)? .ok_or(anyhow!("row to update missing"))?; let before = before.as_object(); let after = entry.as_object(); txn.set(&self.key(row), bytemuck::cast_slice(entry.0.as_slice()))?; for idx in &self.indices { if !idx.compare(before, after) { idx.remove(txn, row, before)?; idx.add(txn, row, after)?; } } Ok(()) } pub fn query( &self, txn: &dyn ReadTransaction, query: Query, ) -> Box>> { todo!() } pub fn query_single(&self, txn: &dyn ReadTransaction, query: Query) -> Result> { self.query(txn, query).next().transpose() } } #[cfg(test)] mod test { use crate::{ table::Table, test_shared::{NAME, new_bob}, }; use anyhow::Result; use jellykv::Database; #[test] pub fn insert_get() -> Result<()> { let db = jellykv::memory::new(); let table = Table::new(5); let mut bob_row = 0; db.write_transaction(&mut |txn| { bob_row = table.insert(txn, new_bob())?; Ok(()) })?; let mut bob = None; db.read_transaction(&mut |txn| { bob = table.get(txn, bob_row)?; Ok(()) })?; assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Bob"); Ok(()) } #[test] pub fn update() -> Result<()> { let db = jellykv::memory::new(); let table = Table::new(5); let mut bob_row = 0; let mut bob = None; db.write_transaction(&mut |txn| { bob_row = table.insert(txn, new_bob())?; Ok(()) })?; db.write_transaction(&mut |txn| { let better_bob = new_bob().as_object().insert(NAME, "Better Bob"); table.update(txn, bob_row, better_bob)?; Ok(()) })?; db.read_transaction(&mut |txn| { bob = table.get(txn, bob_row)?; Ok(()) })?; assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Better Bob"); Ok(()) } }