use anyhow::Result;
use crate::ctx::FrozenContext;
use crate::idx::IndexKeyBase;
use crate::kvs::Transaction;
use crate::val::RecordIdKey;
pub type DocId = u64;
#[derive(Debug, PartialEq)]
pub(super) enum Resolved {
New(DocId),
Existing(DocId),
}
impl Resolved {
pub(in crate::idx) fn doc_id(&self) -> DocId {
match self {
Resolved::New(doc_id) => *doc_id,
Resolved::Existing(doc_id) => *doc_id,
}
}
}
pub(crate) struct SeqDocIds {
ikb: IndexKeyBase,
}
impl SeqDocIds {
pub(in crate::idx) fn new(ikb: IndexKeyBase) -> Self {
Self {
ikb,
}
}
pub(in crate::idx) async fn get_doc_id(
&self,
tx: &Transaction,
id: &RecordIdKey,
) -> Result<Option<DocId>> {
let id_key = self.ikb.new_id_key(id.clone());
tx.get(&id_key, None).await
}
pub(in crate::idx) async fn resolve_doc_id(
&self,
ctx: &FrozenContext,
id: RecordIdKey,
) -> Result<Resolved> {
let id_key = self.ikb.new_id_key(id.clone());
let tx = ctx.tx();
if let Some(doc_id) = tx.get(&id_key, None).await? {
return Ok(Resolved::Existing(doc_id));
}
let new_doc_id = ctx
.try_get_sequences()?
.next_fts_doc_id(Some(ctx), self.ikb.clone(), ctx.config.fts_doc_ids_batch_size)
.await? as DocId;
{
tx.set(&id_key, &new_doc_id).await?;
}
{
let k = self.ikb.new_ii_key(new_doc_id);
tx.set(&k, &id).await?;
}
Ok(Resolved::New(new_doc_id))
}
pub(in crate::idx) async fn get_id(
ikb: &IndexKeyBase,
tx: &Transaction,
doc_id: DocId,
) -> Result<Option<RecordIdKey>> {
tx.get(&ikb.new_ii_key(doc_id), None).await
}
pub(in crate::idx) async fn remove_doc_id(
&self,
tx: &Transaction,
doc_id: DocId,
) -> Result<()> {
let k = self.ikb.new_ii_key(doc_id);
if let Some(id) = tx.get(&k, None).await? {
tx.del(&self.ikb.new_id_key(id)).await?;
tx.del(&k).await?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::catalog::{DatabaseId, IndexId, NamespaceId};
use crate::ctx::FrozenContext;
use crate::idx::IndexKeyBase;
use crate::idx::seqdocids::{DocId, Resolved, SeqDocIds};
use crate::kvs::LockType::Optimistic;
use crate::kvs::TransactionType::{Read, Write};
use crate::kvs::{Datastore, TransactionType};
use crate::val::{RecordIdKey, TableName};
const TEST_NS_ID: NamespaceId = NamespaceId(1);
const TEST_DB_ID: DatabaseId = DatabaseId(1);
const TEST_TB: &str = "test_tb";
const TEST_IX_ID: IndexId = IndexId(1);
async fn new_operation(ds: &Datastore, tt: TransactionType) -> (FrozenContext, SeqDocIds) {
let mut ctx = ds.setup_ctx().unwrap();
let tx = ds.transaction(tt, Optimistic).await.unwrap();
let ikb = IndexKeyBase::new(TEST_NS_ID, TEST_DB_ID, TEST_TB.into(), TEST_IX_ID);
ctx.set_transaction(tx.into());
let d = SeqDocIds::new(ikb);
(ctx.freeze(), d)
}
async fn finish(ctx: FrozenContext) {
ctx.tx().commit().await.unwrap();
}
async fn check_get_doc_key_id(ctx: &FrozenContext, d: &SeqDocIds, doc_id: DocId, key: &str) {
let tx = ctx.tx();
let id = RecordIdKey::String(key.into());
assert_eq!(SeqDocIds::get_id(&d.ikb, &tx, doc_id).await.unwrap(), Some(id.clone()));
assert_eq!(d.get_doc_id(&tx, &id).await.unwrap(), Some(doc_id));
}
#[tokio::test]
async fn test_resolve_doc_id() {
let ds = Datastore::new("memory").await.unwrap();
{
let (ctx, d) = new_operation(&ds, Write).await;
let doc_id = d.resolve_doc_id(&ctx, "Foo".to_owned().into()).await.unwrap();
assert_eq!(doc_id, Resolved::New(0));
finish(ctx).await;
let (ctx, d) = new_operation(&ds, Read).await;
check_get_doc_key_id(&ctx, &d, 0, "Foo").await;
}
{
let (tx, d) = new_operation(&ds, Write).await;
let doc_id = d.resolve_doc_id(&tx, "Foo".to_owned().into()).await.unwrap();
assert_eq!(doc_id, Resolved::Existing(0));
finish(tx).await;
let (tx, d) = new_operation(&ds, Read).await;
check_get_doc_key_id(&tx, &d, 0, "Foo").await;
}
{
let (tx, d) = new_operation(&ds, Write).await;
let doc_id = d.resolve_doc_id(&tx, "Bar".to_owned().into()).await.unwrap();
assert_eq!(doc_id, Resolved::New(1));
finish(tx).await;
let (tx, d) = new_operation(&ds, Read).await;
check_get_doc_key_id(&tx, &d, 1, "Bar").await;
}
{
let (tx, d) = new_operation(&ds, Write).await;
assert_eq!(
d.resolve_doc_id(&tx, "Foo".to_owned().into()).await.unwrap(),
Resolved::Existing(0)
);
assert_eq!(
d.resolve_doc_id(&tx, "Hello".to_owned().into()).await.unwrap(),
Resolved::New(2)
);
assert_eq!(
d.resolve_doc_id(&tx, "Bar".to_owned().into()).await.unwrap(),
Resolved::Existing(1)
);
assert_eq!(
d.resolve_doc_id(&tx, "World".to_owned().into()).await.unwrap(),
Resolved::New(3)
);
finish(tx).await;
let (tx, d) = new_operation(&ds, Read).await;
check_get_doc_key_id(&tx, &d, 0, "Foo").await;
check_get_doc_key_id(&tx, &d, 1, "Bar").await;
check_get_doc_key_id(&tx, &d, 2, "Hello").await;
check_get_doc_key_id(&tx, &d, 3, "World").await;
}
{
let (tx, d) = new_operation(&ds, Write).await;
assert_eq!(
d.resolve_doc_id(&tx, "Foo".to_owned().into()).await.unwrap(),
Resolved::Existing(0)
);
assert_eq!(
d.resolve_doc_id(&tx, "Bar".to_owned().into()).await.unwrap(),
Resolved::Existing(1)
);
assert_eq!(
d.resolve_doc_id(&tx, "Hello".to_owned().into()).await.unwrap(),
Resolved::Existing(2)
);
assert_eq!(
d.resolve_doc_id(&tx, "World".to_owned().into()).await.unwrap(),
Resolved::Existing(3)
);
finish(tx).await;
let (tx, d) = new_operation(&ds, Read).await;
check_get_doc_key_id(&tx, &d, 0, "Foo").await;
check_get_doc_key_id(&tx, &d, 1, "Bar").await;
check_get_doc_key_id(&tx, &d, 2, "Hello").await;
check_get_doc_key_id(&tx, &d, 3, "World").await;
}
}
#[tokio::test]
async fn test_remove_doc_id() {
let ds = Datastore::new("memory").await.unwrap();
{
let (tx, d) = new_operation(&ds, Write).await;
assert_eq!(
d.resolve_doc_id(&tx, "Foo".to_owned().into()).await.unwrap(),
Resolved::New(0)
);
assert_eq!(
d.resolve_doc_id(&tx, "Bar".to_owned().into()).await.unwrap(),
Resolved::New(1)
);
finish(tx).await;
}
{
let (ctx, d) = new_operation(&ds, Write).await;
d.remove_doc_id(&ctx.tx(), 2).await.unwrap();
d.remove_doc_id(&ctx.tx(), 0).await.unwrap();
finish(ctx).await;
}
{
let (ctx, d) = new_operation(&ds, Read).await;
assert_eq!(d.get_doc_id(&ctx.tx(), &"Foo".to_owned().into()).await.unwrap(), None);
assert_eq!(d.get_doc_id(&ctx.tx(), &"Bar".to_owned().into()).await.unwrap(), Some(1));
}
{
let (ctx, d) = new_operation(&ds, Write).await;
assert_eq!(
d.resolve_doc_id(&ctx, "Hello".to_owned().into()).await.unwrap(),
Resolved::New(2)
);
finish(ctx).await;
}
{
let (ctx, d) = new_operation(&ds, Read).await;
assert_eq!(d.get_doc_id(&ctx.tx(), &"Foo".to_owned().into()).await.unwrap(), None);
assert_eq!(d.get_doc_id(&ctx.tx(), &"Bar".to_owned().into()).await.unwrap(), Some(1));
assert_eq!(d.get_doc_id(&ctx.tx(), &"Hello".to_owned().into()).await.unwrap(), Some(2));
}
{
let (ctx, d) = new_operation(&ds, Write).await;
d.remove_doc_id(&ctx.tx(), 1).await.unwrap();
finish(ctx).await;
}
{
let (ctx, d) = new_operation(&ds, Read).await;
assert_eq!(d.get_doc_id(&ctx.tx(), &"Foo".to_owned().into()).await.unwrap(), None);
assert_eq!(d.get_doc_id(&ctx.tx(), &"Bar".to_owned().into()).await.unwrap(), None);
assert_eq!(d.get_doc_id(&ctx.tx(), &"Hello".to_owned().into()).await.unwrap(), Some(2));
}
{
let (ctx, d) = new_operation(&ds, Write).await;
assert_eq!(
d.resolve_doc_id(&ctx, "World".to_owned().into()).await.unwrap(),
Resolved::New(3)
);
finish(ctx).await;
}
{
let (ctx, d) = new_operation(&ds, Read).await;
assert_eq!(d.get_doc_id(&ctx.tx(), &"Foo".to_owned().into()).await.unwrap(), None);
assert_eq!(d.get_doc_id(&ctx.tx(), &"Bar".to_owned().into()).await.unwrap(), None);
assert_eq!(d.get_doc_id(&ctx.tx(), &"Hello".to_owned().into()).await.unwrap(), Some(2));
assert_eq!(d.get_doc_id(&ctx.tx(), &"World".to_owned().into()).await.unwrap(), Some(3));
}
{
let (ctx, d) = new_operation(&ds, Write).await;
d.remove_doc_id(&ctx.tx(), 1).await.unwrap();
d.remove_doc_id(&ctx.tx(), 2).await.unwrap();
d.remove_doc_id(&ctx.tx(), 3).await.unwrap();
finish(ctx).await;
}
{
let (ctx, _) = new_operation(&ds, Read).await;
let tx = ctx.tx();
let tb = TableName::from(TEST_TB);
for id in ["Foo", "Bar", "Hello", "World"] {
let id = crate::key::index::id::Id::new(
TEST_NS_ID,
TEST_DB_ID,
&tb,
TEST_IX_ID,
RecordIdKey::String(id.into()),
);
assert!(!tx.exists(&id, None).await.unwrap());
}
let ikb = IndexKeyBase::new(TEST_NS_ID, TEST_DB_ID, TEST_TB.into(), TEST_IX_ID);
for doc_id in 0..=3 {
assert_eq!(SeqDocIds::get_id(&ikb, &tx, doc_id).await.unwrap(), None);
}
}
}
}