uqa-engine 0.1.9

Engine: schema-aware table store, catalog restore, transactions
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

use super::*;

#[test]
fn value_to_usize_rejects_non_finite_fractional_and_out_of_range_floats() {
    assert_eq!(value_to_usize(&Value::Float(42.0)).unwrap(), 42);
    for value in [f64::NAN, f64::INFINITY, -1.0, 1.5] {
        assert!(value_to_usize(&Value::Float(value)).is_err());
    }
    let exponent = i32::try_from(usize::BITS).unwrap();
    assert!(value_to_usize(&Value::Float(2.0_f64.powi(exponent))).is_err());
}

#[test]
fn persisted_ivf_parameters_reject_invalid_values() {
    let invalid = BTreeMap::from([("lists".to_string(), "not-a-number".to_string())]);
    assert!(IVFIndexParams::from_catalog_map(&invalid).is_err());

    let zero = BTreeMap::from([("probes".to_string(), "0".to_string())]);
    assert!(IVFIndexParams::from_catalog_map(&zero).is_err());
}

#[test]
fn document_id_watermark_represents_and_reports_exhaustion_without_wrapping() {
    let engine = Engine::new();
    engine.create_default_table("docs", Vec::new()).unwrap();
    let table = engine.table("docs").unwrap().expect("table");
    *table.next_id.lock() = u128::from(u64::MAX);

    assert_eq!(engine.allocate_next_id("docs").unwrap(), u64::MAX);
    let error = engine.allocate_next_id("docs").unwrap_err();
    assert!(error.to_string().contains("document id space"), "{error}");
    assert_eq!(*table.next_id.lock(), u128::from(u64::MAX) + 1);

    let second = Engine::new();
    second.create_default_table("docs", Vec::new()).unwrap();
    second.advance_next_id("docs", u64::MAX).unwrap();
    let error = second.allocate_next_id("docs").unwrap_err();
    assert!(error.to_string().contains("document id space"), "{error}");
}

#[test]
fn vector_backfill_reports_invalid_values_instead_of_skipping_them() {
    let engine = Engine::new();
    engine.create_default_table("docs", Vec::new()).unwrap();
    engine
        .add_document(
            "docs",
            1,
            doc([("embedding", Value::Str("not-a-vector".into()))]),
        )
        .unwrap();

    let error = engine
        .create_vector_field("docs", "embedding", 2)
        .unwrap_err();
    assert!(error.to_string().contains("expected vector"), "{error}");
    let unregistered = engine
        .add_vector("docs", 1, "embedding", vec![1.0, 0.0])
        .unwrap_err();
    assert!(matches!(unregistered, SQLError::TypeMismatch(_)));
}

#[test]
fn vector_field_registration_distinguishes_absence_noop_and_dimension_mismatch() {
    let engine = Engine::new();
    let missing = engine
        .create_vector_field("missing", "embedding", 2)
        .unwrap_err();
    assert!(missing.to_string().contains("does not exist"), "{missing}");

    engine.create_default_table("docs", Vec::new()).unwrap();
    assert!(engine.create_vector_field("docs", "embedding", 2).unwrap());
    assert!(!engine.create_vector_field("docs", "embedding", 2).unwrap());
    let mismatch = engine
        .create_vector_field("docs", "embedding", 3)
        .unwrap_err();
    assert!(mismatch.to_string().contains("dimension 2"), "{mismatch}");
    assert!(mismatch.to_string().contains("requested 3"), "{mismatch}");
}

#[test]
fn direct_vector_writes_reject_unknown_tables_and_unregistered_fields() {
    let engine = Engine::new();
    let missing = engine
        .add_vector("missing", 1, "embedding", vec![1.0, 0.0])
        .unwrap_err();
    assert!(matches!(missing, SQLError::UnknownTable(_)));

    engine.create_default_table("docs", Vec::new()).unwrap();
    let unregistered = engine
        .add_vector("docs", 1, "embedding", vec![1.0, 0.0])
        .unwrap_err();
    assert!(matches!(unregistered, SQLError::TypeMismatch(_)));
    let unregistered_many = engine
        .add_vector_values("docs", 1, "embedding", vec![vec![1.0, 0.0]])
        .unwrap_err();
    assert!(matches!(unregistered_many, SQLError::TypeMismatch(_)));

    assert!(engine.create_vector_field("docs", "embedding", 2).unwrap());
    assert!(engine
        .add_vector("docs", 1, "embedding", vec![1.0, 0.0])
        .unwrap());
    assert!(engine
        .add_vector_values("docs", 1, "embedding", vec![vec![0.0, 1.0]])
        .unwrap());
}

#[test]
fn table_introspection_distinguishes_unknown_tables_from_missing_columns() {
    let engine = Engine::new();
    for error in [
        engine.try_table_columns("missing").unwrap_err(),
        engine.try_table_has_column("missing", "value").unwrap_err(),
        engine.column_type("missing", "value").unwrap_err(),
    ] {
        assert!(error.to_string().contains("does not exist"), "{error}");
    }

    engine.create_default_table("docs", Vec::new()).unwrap();
    assert!(engine.try_table_columns("docs").unwrap().is_empty());
    assert!(!engine.try_table_has_column("docs", "value").unwrap());
    assert_eq!(engine.column_type("docs", "value").unwrap(), None);

    let sql_error = engine.sql("SELECT * FROM missing", &[]).unwrap_err();
    assert!(sql_error.to_string().contains("missing"), "{sql_error}");
    assert!(
        sql_error.to_string().contains("does not exist"),
        "{sql_error}"
    );
}

#[test]
fn direct_schema_mutations_reject_missing_relations_columns_and_duplicates() {
    let engine = Engine::new();
    let column = uqa_sql::ast::ColumnDef {
        name: "value".into(),
        ty: uqa_sql::ast::ColumnType::Integer,
        object_id: None,
        missing_value: None,
        primary_key: false,
        not_null: false,
        not_null_explicit: false,
        not_null_name: None,
        not_null_validated: true,
        not_null_no_inherit: false,
        auto_increment: None,
        unique: false,
        default: None,
        generated: None,
        check: None,
        check_name: None,
        check_enforced: true,
        check_validated: true,
        check_no_inherit: false,
        references: None,
    };

    for error in [
        engine
            .register_column("missing", column.clone())
            .unwrap_err(),
        engine
            .set_column_default("missing", "value", None)
            .unwrap_err(),
        engine
            .set_column_not_null("missing", "value", true)
            .unwrap_err(),
        engine
            .set_column_type("missing", "value", &uqa_sql::ast::ColumnType::Boolean)
            .unwrap_err(),
        engine
            .try_column_default_expr("missing", "value")
            .unwrap_err(),
        engine.advance_next_id("missing", 1).unwrap_err(),
        engine
            .refresh_value_indexes_for_table("missing")
            .unwrap_err(),
        engine.try_persist_table_schema("missing").unwrap_err(),
        engine
            .try_rebuild_vector_index_for_column("missing", "embedding", 2)
            .unwrap_err(),
    ] {
        assert!(error.to_string().contains("does not exist"), "{error}");
    }

    engine.create_default_table("docs", Vec::new()).unwrap();
    engine.register_column("docs", column.clone()).unwrap();
    let duplicate = engine.register_column("docs", column).unwrap_err();
    assert!(
        duplicate.to_string().contains("already exists"),
        "{duplicate}"
    );
    for error in [
        engine
            .set_column_default("docs", "absent", None)
            .unwrap_err(),
        engine
            .set_column_not_null("docs", "absent", true)
            .unwrap_err(),
        engine
            .set_column_type("docs", "absent", &uqa_sql::ast::ColumnType::Boolean)
            .unwrap_err(),
    ] {
        assert!(error.to_string().contains("column `absent`"), "{error}");
    }

    assert!(engine.set_column_default("docs", "value", None).unwrap());
    assert!(engine.set_column_not_null("docs", "value", true).unwrap());
    assert!(engine
        .set_column_type("docs", "value", &uqa_sql::ast::ColumnType::Boolean)
        .unwrap());
    assert_eq!(
        engine.column_type("docs", "value").unwrap(),
        Some(uqa_sql::ast::ColumnType::Boolean)
    );
}

#[test]
fn table_metadata_getters_reject_unknown_relations() {
    let engine = Engine::new();
    assert!(engine.describe_table("missing").unwrap().is_none());
    for error in [
        engine.auto_increment_column("missing").unwrap_err(),
        engine.try_check_constraints("missing").unwrap_err(),
        engine.try_foreign_keys("missing").unwrap_err(),
        engine.try_unique_columns("missing").unwrap_err(),
        engine.try_key_constraints("missing").unwrap_err(),
        engine.try_referrers_to("missing").unwrap_err(),
        engine.try_column_stats("missing").unwrap_err(),
    ] {
        assert!(error.to_string().contains("does not exist"), "{error}");
    }

    engine.create_default_table("docs", Vec::new()).unwrap();
    assert_eq!(engine.auto_increment_column("docs").unwrap(), None);
    assert!(engine.try_check_constraints("docs").unwrap().is_empty());
    assert!(engine.try_foreign_keys("docs").unwrap().is_empty());
    assert!(engine.try_unique_columns("docs").unwrap().is_empty());
    assert!(engine.try_key_constraints("docs").unwrap().is_empty());
    assert!(engine.try_referrers_to("docs").unwrap().is_empty());
    assert!(engine.try_column_stats("docs").unwrap().is_empty());
}

#[test]
fn document_mutations_distinguish_unknown_tables_from_missing_documents() {
    let engine = Engine::new();
    let updates = BTreeMap::from([("value".to_string(), Value::Int(1))]);
    let vectors = BTreeMap::new();
    for error in [
        engine
            .update_document_fields("missing", 1, updates.clone(), vectors.clone())
            .unwrap_err(),
        engine
            .patch_document_fields("missing", 1, &updates, &vectors)
            .unwrap_err(),
        engine
            .rewrite_prepared_document("missing", 1, Document::new())
            .unwrap_err(),
        engine.delete_document("missing", 1).unwrap_err(),
    ] {
        assert!(matches!(error, SQLError::UnknownTable(_)), "{error}");
    }

    engine.create_default_table("docs", Vec::new()).unwrap();
    assert!(!engine
        .update_document_fields("docs", 1, updates.clone(), vectors.clone())
        .unwrap());
    assert!(!engine
        .patch_document_fields("docs", 1, &updates, &vectors)
        .unwrap());
    engine.delete_document("docs", 1).unwrap();
}

#[test]
fn tensor_backfill_reports_inner_dimension_mismatch_and_allows_null() {
    let tensor_column = uqa_sql::ast::ColumnDef {
        name: "embedding".into(),
        ty: uqa_sql::ast::ColumnType::Tensor(2),
        object_id: None,
        missing_value: None,
        primary_key: false,
        not_null: false,
        not_null_explicit: false,
        not_null_name: None,
        not_null_validated: true,
        not_null_no_inherit: false,
        auto_increment: None,
        unique: false,
        default: None,
        generated: None,
        check: None,
        check_name: None,
        check_enforced: true,
        check_validated: true,
        check_no_inherit: false,
        references: None,
    };

    let engine = Engine::new();
    engine.create_default_table("bad", Vec::new()).unwrap();
    engine
        .register_column("bad", tensor_column.clone())
        .unwrap();
    engine
        .add_document(
            "bad",
            1,
            doc([(
                "embedding",
                Value::List(vec![Value::List(vec![Value::Float(1.0)])]),
            )]),
        )
        .unwrap();
    let error = engine
        .create_vector_field("bad", "embedding", 2)
        .unwrap_err();
    assert!(
        error
            .to_string()
            .contains("vector dimension mismatch: expected 2, got 1"),
        "{error}"
    );

    let nullable = Engine::new();
    nullable
        .create_default_table("nullable", Vec::new())
        .unwrap();
    nullable.register_column("nullable", tensor_column).unwrap();
    nullable
        .add_document("nullable", 1, doc([("embedding", Value::Null)]))
        .unwrap();
    assert!(nullable
        .create_vector_field("nullable", "embedding", 2)
        .unwrap());
}