sim-relation-core 0.2.0

Open relational domain, row, and canonical identity records for SIM.
Documentation
use sim_kernel::{Datum, Ref, Symbol};
use sim_relation_core::*;

// conformance: open relational domains, rows, canonical identity, and storage edges.

fn domain(name: &str) -> DomainId {
    DomainId::new(Symbol::qualified("test", name)).unwrap()
}

#[test]
fn names_are_sql_dialect_neutral_but_structurally_valid() {
    assert!(TableName::new(Symbol::new("select / odd-name \"quoted\"")).is_ok());
    assert_eq!(TableName::new(Symbol::new("")), Err(NameError::Empty));
    assert_eq!(
        TableName::new(Symbol::new("bad\nname")),
        Err(NameError::Control)
    );
}

#[test]
fn catalogs_reject_duplicates_bad_refs_and_incoherent_traits() {
    let id = domain("uuid");
    let spec = DomainSpec::new(
        id.clone(),
        StorageRepr::Text,
        Ref::Symbol(Symbol::new("uuid-shape")),
        [DomainTrait::Equatable],
    )
    .unwrap();
    assert!(matches!(
        DomainCatalog::new([spec.clone(), spec]),
        Err(DomainError::DuplicateId(_))
    ));
    assert_eq!(
        DomainSpec::new(
            id.clone(),
            StorageRepr::Text,
            Ref::Handle(sim_kernel::HandleId(1)),
            []
        ),
        Err(DomainError::InvalidShapeRef)
    );
    assert_eq!(
        DomainSpec::new(
            id,
            StorageRepr::Text,
            Ref::Symbol(Symbol::new("shape")),
            [DomainTrait::Ordered]
        ),
        Err(DomainError::IncoherentTraits)
    );
}

#[test]
fn typed_null_is_absence_and_rows_enforce_types() {
    let id = domain("text");
    let ty = RowType::new([FieldType {
        name: FieldName::new(Symbol::new("nickname")).unwrap(),
        domain: id.clone(),
        nullable: true,
    }])
    .unwrap();
    let row = Row::new(ty, [Cell::null(id)]).unwrap();
    assert_eq!(row.cells()[0].value(), None);
    assert!(matches!(row.to_datum(), Datum::Node { .. }));
}

#[test]
fn base_storage_conversions_are_exact_and_float_edges_are_closed() {
    let builtins = DomainCatalog::new([
        BaseDomain::Bool.spec(),
        BaseDomain::I64.spec(),
        BaseDomain::F64.spec(),
        BaseDomain::Text.spec(),
        BaseDomain::Bytes.spec(),
    ])
    .unwrap();
    assert_eq!(builtins.iter().count(), 5);
    for (base, value) in [
        (BaseDomain::Bool, StorageValue::Bool(true)),
        (BaseDomain::I64, StorageValue::I64(i64::MIN)),
        (BaseDomain::F64, StorageValue::F64(-0.0)),
        (BaseDomain::Text, StorageValue::Text("hello".into())),
        (BaseDomain::Bytes, StorageValue::Bytes(vec![0, 255])),
    ] {
        let datum = base.to_datum(value.clone()).unwrap();
        assert_eq!(
            base.from_datum(&datum).unwrap(),
            value_with_canonical_zero(value)
        );
    }
    assert_eq!(
        BaseDomain::F64.to_datum(StorageValue::F64(f64::NAN)),
        Err(DomainError::NonFiniteFloat)
    );
    assert_eq!(
        BaseDomain::F64.to_datum(StorageValue::F64(f64::INFINITY)),
        Err(DomainError::NonFiniteFloat)
    );
    assert_eq!(
        BaseDomain::F64.to_datum(StorageValue::F64(-0.0)).unwrap(),
        BaseDomain::F64.to_datum(StorageValue::F64(0.0)).unwrap()
    );
}

fn value_with_canonical_zero(value: StorageValue) -> StorageValue {
    match value {
        StorageValue::F64(v) => StorageValue::F64(v + 0.0),
        other => other,
    }
}

#[test]
fn custom_domains_need_no_provider_change_and_identity_is_kernel_identity() {
    let uuid = DomainSpec::new(
        domain("uuid"),
        StorageRepr::Text,
        Ref::Symbol(Symbol::new("uuid-shape")),
        [DomainTrait::Equatable],
    )
    .unwrap();
    let catalog = DomainCatalog::new([uuid.clone()]).unwrap();
    assert_eq!(catalog.get(uuid.id()), Some(&uuid));
    assert_eq!(uuid.card_datum(), uuid.lisp_datum());
    assert_eq!(
        RelationId::of(&uuid).unwrap().content_id(),
        &uuid.to_datum().content_id().unwrap()
    );
    assert_eq!(
        RelationId::of(&uuid).unwrap(),
        RelationId::of(&uuid.clone()).unwrap()
    );
}