use std::collections::VecDeque;
use std::sync::Arc;
use tessarikv_constants as limits;
use tessarikv_constants::LEVELS;
use tessarikv_format::{KeyspaceRecord, ManifestEdit, TableRef};
use tessarikv_types::KeyspaceId;
use tokio::sync::oneshot;
use super::compaction::Compactions;
use super::compaction::manual::Request;
use super::installer::Flusher;
use super::view::View;
use crate::memtable::Memtable;
use crate::options::DEFAULT_NAME;
use crate::version::Version;
use crate::{Error, KeyspaceOptions, Result};
pub(crate) enum Command {
Compact(Request),
Create(Create),
Drop(DropKeyspace),
Checkpoint(oneshot::Sender<Pinned>),
}
pub(crate) type Pinned = (Arc<Version>, Arc<View>);
pub(crate) struct Create {
pub(crate) name: String,
pub(crate) options: KeyspaceOptions,
pub(crate) done: oneshot::Sender<Result<KeyspaceId>>,
}
pub(crate) struct DropKeyspace {
pub(crate) keyspace: u32,
pub(crate) done: oneshot::Sender<Result<()>>,
}
enum Change {
Create(Create),
Drop(DropKeyspace),
Checkpoint(oneshot::Sender<Pinned>),
}
#[derive(Default)]
pub(super) struct Catalog {
queued: VecDeque<Change>,
}
impl Catalog {
pub(super) fn create(&mut self, create: Create) {
self.queued.push_back(Change::Create(create));
}
pub(super) fn drop_keyspace(&mut self, drop: DropKeyspace) {
self.queued.push_back(Change::Drop(drop));
}
pub(super) fn checkpoint(&mut self, reply: oneshot::Sender<Pinned>) {
self.queued.push_back(Change::Checkpoint(reply));
}
pub(super) async fn step(
&mut self,
flusher: &mut Flusher,
compactions: &mut Compactions,
) -> Result<bool> {
let mut published = false;
while flusher.manifest.is_some()
&& let Some(change) = self.queued.pop_front()
{
published |= match change {
Change::Create(create) => created(flusher, create).await?,
Change::Drop(drop) => dropped(flusher, compactions, drop).await?,
Change::Checkpoint(reply) => {
if let Some(manifest) = flusher.manifest.as_ref() {
let _ = reply.send((manifest.version(), flusher.view.load_full()));
}
false
}
};
}
Ok(published)
}
}
async fn created(flusher: &mut Flusher, create: Create) -> Result<bool> {
let Create {
name,
options,
done,
} = create;
let Some(manifest) = flusher.manifest.as_ref() else {
return Ok(false);
};
let version = manifest.version();
let held = version.keyspaces().count();
let id = version.next_keyspace().max(1);
let checked =
check_new(&name, version.keyspaces().map(|(_, held)| held), held).and_then(|()| {
id.checked_add(1)
.ok_or(refuse("its keyspace ids are exhausted"))
});
let next = match checked {
Ok(next) => next,
Err(refused) => {
let _ = done.send(Err(refused));
return Ok(false);
}
};
let added = vec![KeyspaceRecord {
id,
name: name.clone(),
}];
append(
flusher,
ManifestEdit {
added_keyspaces: added,
next_keyspace: Some(next),
..ManifestEdit::default()
},
)
.await?;
let table = Memtable::sized(options.memtable_bytes());
flusher.keyspaces.push((id, options));
let current = flusher.view.load_full();
flusher
.view
.store(Arc::new(current.with_keyspace(id, &name, table)?));
let _ = done.send(Ok(KeyspaceId::new(id)));
Ok(true)
}
async fn dropped(
flusher: &mut Flusher,
compactions: &mut Compactions,
drop: DropKeyspace,
) -> Result<bool> {
let DropKeyspace { keyspace, done } = drop;
let Some(manifest) = flusher.manifest.as_ref() else {
return Ok(false);
};
let version = manifest.version();
if keyspace == 0 {
let _ = done.send(Err(Error::InvalidArgument {
what: "dropping a keyspace",
reason: "the default keyspace is never dropped",
}));
return Ok(false);
}
if !version.keyspaces().any(|(id, _)| id == keyspace) {
let _ = done.send(Err(Error::UnknownKeyspace { keyspace }));
return Ok(false);
}
let removed = (0..LEVELS)
.flat_map(|level| version.tables(keyspace, level))
.map(|table| TableRef {
keyspace,
level: table.level,
id: table.id,
})
.collect();
append(
flusher,
ManifestEdit {
removed,
dropped_keyspaces: vec![keyspace],
..ManifestEdit::default()
},
)
.await?;
flusher.keyspaces.retain(|(id, _)| *id != keyspace);
let (view, files) = flusher.view.load().without_keyspace(keyspace);
flusher.view.store(Arc::new(view));
compactions.retire(files, &flusher.dir);
let _ = done.send(Ok(()));
Ok(true)
}
async fn append(flusher: &mut Flusher, edit: ManifestEdit) -> Result<()> {
let mut manifest = flusher.manifest.take().ok_or(Error::FlushFailed)?;
let (manifest, appended) = tokio::task::spawn_blocking(move || {
let appended = manifest.append(&edit);
(manifest, appended)
})
.await
.map_err(|_| Error::FlushFailed)?;
flusher.manifest = Some(manifest);
appended
}
pub(super) fn refuse(reason: &'static str) -> Error {
Error::InvalidArgument {
what: "creating a keyspace",
reason,
}
}
pub(super) fn check_new<'a>(
name: &str,
mut held: impl Iterator<Item = &'a str>,
count: usize,
) -> Result<()> {
if name.is_empty() || name.len() > limits::MAX_KEYSPACE_NAME_BYTES {
return Err(refuse("a keyspace name is empty or too long"));
}
if name == DEFAULT_NAME || held.any(|taken| taken == name) {
return Err(refuse("a keyspace already has that name"));
}
let most = usize::try_from(limits::MAX_KEYSPACES).unwrap_or(usize::MAX);
if count.saturating_add(1) >= most {
return Err(refuse("the database holds as many keyspaces as it may"));
}
Ok(())
}