kevy-index 4.1.1

Declarative secondary indexes over prefix domains: range/unique kinds, derived-by-construction, cursor pagination.
Documentation
//! TableSpec / TableCatalog / compile / wire-grammar tests.

use super::*;
use crate::catalog::{IndexKind, ValType};
use crate::table_wire::{TABLE_DECLARE_USAGE, parse_table_declare};

fn declare(parts: &[&str]) -> Result<TableSpec, String> {
    let argv: Vec<&[u8]> = parts.iter().map(|s| s.as_bytes()).collect();
    parse_table_declare(&argv)
}

const USER: &[&str] = &[
    "TABLE.DECLARE", "user", "PREFIX", "user:", "PK", "id",
    "COLUMN", "id", "str", "COLUMN", "name", "str", "COLUMN", "age", "i64",
    "COLUMN", "dept", "str",
    "INDEX", "age", "RANGE", "VALUES", "dept", "name",
    "INDEX", "dept", "UNIQUE",
    "ORDERPATH", "recent_by_dept", "ON", "dept", "THEN", "age", "DESC",
];

#[test]
fn full_declare_parses_and_compiles() {
    let spec = declare(USER).expect("parses");
    assert_eq!(spec.columns.len(), 4);
    assert_eq!(spec.indexes.len(), 2);
    assert_eq!(spec.orderpaths.len(), 1);
    let compiled = compile_table(&spec).expect("valid spec compiles");
    assert_eq!(compiled.len(), 3);
    assert_eq!(compiled[0].name, b"user.age".to_vec());
    assert_eq!(compiled[0].ty, ValType::I64);
    assert_eq!(compiled[0].kind, IndexKind::Range);
    assert_eq!(compiled[0].values.len(), 2);
    assert_eq!(compiled[0].values[0].ty, ValType::Str, "VALUES typed from column decls");
    assert_eq!(compiled[1].name, b"user.dept".to_vec());
    assert_eq!(compiled[1].kind, IndexKind::Unique);
    let op = &compiled[2];
    assert_eq!(op.name, b"user.recent_by_dept".to_vec());
    assert_eq!(op.ty, ValType::Str);
    assert_eq!(op.kind, IndexKind::Range);
    let cols = op.composite.as_ref().expect("composite");
    assert_eq!(cols.len(), 2);
    assert_eq!((cols[0].name.as_slice(), cols[0].ty, cols[0].desc), (b"dept".as_slice(), ValType::Str, false));
    assert_eq!((cols[1].name.as_slice(), cols[1].ty, cols[1].desc), (b"age".as_slice(), ValType::I64, true));
    // Every compiled spec is admissible as-is.
    let mut cat = crate::Catalog::new();
    for s in compiled {
        cat.create(s).expect("compiled specs admit");
    }
}

#[test]
fn every_grammar_refusal_is_named() {
    let e = |parts: &[&str]| declare(parts).unwrap_err();
    assert_eq!(e(&["TABLE.DECLARE", "t", "PREFIX", "p:"]), TABLE_DECLARE_USAGE);
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "uuid"]),
        "ERR COLUMN type must be i64|f64|str"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id",
            "COLUMN", "id", "str", "COLUMN", "id", "i64"]),
        "ERR duplicate COLUMN 'id'"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "nope", "COLUMN", "id", "str"]),
        "ERR PK column 'nope' is not declared (add COLUMN nope ...)"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "INDEX", "ghost", "RANGE"]),
        "ERR INDEX names unknown column 'ghost'"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "INDEX", "id", "agg"]),
        "ERR INDEX kind must be range|unique"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "INDEX", "id", "RANGE", "INDEX", "id", "UNIQUE"]),
        "ERR duplicate INDEX on column 'id'"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "INDEX", "id", "RANGE", "VALUES", "ghost"]),
        "ERR VALUES names unknown column 'ghost'"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "INDEX", "id", "RANGE", "VALUES", "INDEX", "id", "UNIQUE"]),
        "ERR VALUES needs at least one column"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "ORDERPATH", "op", "ON", "ghost"]),
        "ERR ORDERPATH 'op' names unknown column 'ghost'"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "ORDERPATH", "op", "ON", "id", "ORDERPATH", "op", "ON", "id"]),
        "ERR duplicate ORDERPATH 'op'"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "ORDERPATH", "op", "BY", "id"]),
        "ERR ORDERPATH needs ON <col>"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "", "PK", "id", "COLUMN", "id", "str"]),
        "ERR PREFIX must be non-empty"
    );
    assert_eq!(
        e(&["TABLE.DECLARE", "t", "PREFIX", "p:", "PK", "id", "COLUMN", "id", "str",
            "INDEX", "id", "RANGE", "ORDERPATH", "id", "ON", "id"]),
        "ERR ORDERPATH 'id' collides with INDEX 'id'"
    );
}

#[test]
fn catalog_lifecycle_and_caps() {
    let spec = declare(USER).expect("parses");
    let mut c = TableCatalog::new();
    c.create(spec.clone()).expect("creates");
    assert_eq!(c.create(spec.clone()).unwrap_err(), "ERR table already exists");
    assert!(c.get(b"user").is_some());
    assert_eq!(c.len(), 1);
    assert!(c.drop_table(b"user"));
    assert!(!c.drop_table(b"user"));
    assert!(c.is_empty());
    for i in 0..MAX_TABLES {
        let mut s = spec.clone();
        s.name = format!("t{i}").into_bytes();
        c.create(s).expect("under cap");
    }
    let mut over = spec.clone();
    over.name = b"over".to_vec();
    assert_eq!(c.create(over).unwrap_err(), "ERR table limit reached (64)");
}

#[test]
fn sidecar_round_trips_with_hostile_names() {
    let mut spec = declare(USER).expect("parses");
    spec.name = b"we\tird,ta:ble".to_vec();
    spec.prefix = b"pre\nfix%:".to_vec();
    let mut c = TableCatalog::new();
    c.create(spec.clone()).expect("creates");
    let text = c.to_sidecar();
    let c2 = TableCatalog::from_sidecar(&text).expect("loads");
    assert_eq!(c2.get(&spec.name), Some(&spec), "byte-exact round trip");
    assert!(TableCatalog::from_sidecar("bogus").is_none());
    assert!(
        TableCatalog::from_sidecar("kevy-table-catalog v1\ngarbage line").is_none(),
        "a malformed line refuses the whole load"
    );
}