moraine 0.6.3

A SlateDB-backed catalog for DuckLake
Documentation
use std::{cell::Cell, sync::Arc};

use object_store::memory::InMemory;

use super::*;
use crate::{Catalog, CatalogOptions, ColumnDef, IndexDef, transaction::commit};

#[tokio::test]
async fn range_and_null_scans_recheck_a_definition_removed_during_the_pass() {
    for null_lookup in [false, true] {
        let store = Arc::new(InMemory::new());
        let options = CatalogOptions {
            reader_poll_interval: Duration::from_millis(10),
            ..Default::default()
        };
        let writer = Catalog::open(store.clone(), options.clone()).await.unwrap();
        let ids = Cell::new(None);
        writer
            .commit(|tx| {
                let schema = tx.schema_by_name("main").unwrap().id;
                let table = tx.create_table(
                    schema,
                    "t",
                    &[ColumnDef {
                        name: "value".into(),
                        column_type: "BIGINT".into(),
                        ..Default::default()
                    }],
                )?;
                let index = tx.create_index(
                    table,
                    &IndexDef {
                        name: "by_value".into(),
                        columns: vec![tx.columns_of(table)[0].id],
                        unique: false,
                    },
                    &[],
                )?;
                ids.set(Some((table, index)));
                Ok(())
            })
            .await
            .unwrap();
        writer.close().await.unwrap();
        let (table, index) = ids.get().unwrap();
        let reader = Catalog::open_read_only(store.clone(), options.clone())
            .await
            .unwrap();
        let writer = Catalog::open(store, options).await.unwrap();
        let session = reader.begin_read().await.unwrap();
        let attempts = Cell::new(0);
        let result = reader
            .with_ready_index(session.handle(), table, index, async |info| {
                attempts.set(attempts.get() + 1);
                let head = writer.commit(|tx| tx.drop_index(index)).await?;
                writer.close().await?;
                tokio::time::timeout(Duration::from_secs(10), async {
                    while commit::read_head_value(session.handle())
                        .await
                        .unwrap()
                        .snapshot_id
                        != head.get()
                    {
                        tokio::time::sleep(Duration::from_millis(10)).await;
                    }
                })
                .await
                .unwrap();
                if null_lookup {
                    let key = encode_ordered_values(&[None], &info.directions, &info.nulls)?;
                    index_maintenance::null_prefix_row_ids(
                        session.handle(),
                        index.get(),
                        &key,
                        ScanOrder::Ascending,
                    )
                    .await
                } else {
                    index_maintenance::range_row_ids(
                        session.handle(),
                        index.get(),
                        info.unique,
                        NullOrder::Last,
                        Bound::Unbounded,
                        Bound::Unbounded,
                        ScanOrder::Ascending,
                    )
                    .await
                }
            })
            .await;
        assert!(matches!(result, Err(Error::NotFound(_))));
        assert_eq!(
            attempts.get(),
            1,
            "the next pass rejects the removed definition before scanning"
        );
        session.finish();
        reader.close().await.unwrap();
    }
}