mentatdb 1.18.0

A Datomic-compatible Datalog triple store on SQLite (the embedded Mentat engine; the library is `mentat`)
Documentation
// Copyright 2018 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#[macro_use]
extern crate mentat;

use mentat::conn::Conn;

use core_traits::{Entid, KnownEntid, TypedValue};

use mentat_core::{HasSchema, TxReport};

use mentat_transaction::{Queryable, TermBuilder};

use public_traits::errors::MentatError;

use mentat::entity_builder::BuildTerms;

// In reality we expect the store to hand these out safely.
fn fake_known_entid(e: Entid) -> KnownEntid {
    KnownEntid(e)
}

#[test]
fn test_entity_builder_bogus_entids() {
    let mut builder = TermBuilder::new();
    let e = builder.named_tempid("x");
    let a1 = fake_known_entid(37); // :db/doc
    let a2 = fake_known_entid(999);
    let v = TypedValue::typed_string("Some attribute");
    let ve = fake_known_entid(12345);

    builder.add(e.clone(), a1, v).expect("add succeeded");
    builder
        .add(e.clone(), a2, e.clone())
        .expect("add succeeded, even though it's meaningless");
    builder
        .add(e, a2, ve)
        .expect("add succeeded, even though it's meaningless");
    let (terms, tempids) = builder.build().expect("build succeeded");

    assert_eq!(tempids.len(), 1);
    assert_eq!(terms.len(), 3); // TODO: check the contents?

    // Now try to add them to a real store.
    let mut sqlite = mentat_db::db::new_connection("").unwrap();
    let mut conn = Conn::connect(&mut sqlite).unwrap();
    let mut in_progress = conn
        .begin_transaction(&mut sqlite)
        .expect("begun successfully");

    // This should fail: unrecognized entid.
    match in_progress
        .transact_entities(terms)
        .expect_err("expected transact to fail")
    {
        MentatError::DbError(e) => {
            assert_eq!(
                e.kind(),
                db_traits::errors::DbErrorKind::UnrecognizedEntid(999)
            );
        }
        _ => panic!("Should have rejected the entid."),
    }
}

#[test]
fn test_in_progress_builder() {
    let mut sqlite = mentat_db::db::new_connection("").unwrap();
    let mut conn = Conn::connect(&mut sqlite).unwrap();

    // Give ourselves a schema to work with!
    conn.transact(
        &mut sqlite,
        r#"[
        [:db/add "o" :db/ident :foo/one]
        [:db/add "o" :db/valueType :db.type/long]
        [:db/add "o" :db/cardinality :db.cardinality/one]
        [:db/add "m" :db/ident :foo/many]
        [:db/add "m" :db/valueType :db.type/string]
        [:db/add "m" :db/cardinality :db.cardinality/many]
        [:db/add "r" :db/ident :foo/ref]
        [:db/add "r" :db/valueType :db.type/ref]
        [:db/add "r" :db/cardinality :db.cardinality/one]
    ]"#,
    )
    .unwrap();

    let in_progress = conn
        .begin_transaction(&mut sqlite)
        .expect("begun successfully");

    // We can use this or not!
    let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many");

    let mut builder = in_progress.builder();
    let e_x = builder.named_tempid("x");
    let v_many_1 = TypedValue::typed_string("Some text");
    let v_many_2 = TypedValue::typed_string("Other text");
    builder
        .add(e_x.clone(), kw!(:foo/many), v_many_1)
        .expect("add succeeded");
    builder.add(e_x, a_many, v_many_2).expect("add succeeded");
    builder.commit().expect("commit succeeded");
}

#[test]
fn test_entity_builder() {
    let mut sqlite = mentat_db::db::new_connection("").unwrap();
    let mut conn = Conn::connect(&mut sqlite).unwrap();

    let foo_one = kw!(:foo/one);
    let foo_many = kw!(:foo/many);
    let foo_ref = kw!(:foo/ref);
    let report: TxReport;

    // Give ourselves a schema to work with!
    // Scoped borrow of conn.
    {
        conn.transact(
            &mut sqlite,
            r#"[
            [:db/add "o" :db/ident :foo/one]
            [:db/add "o" :db/valueType :db.type/long]
            [:db/add "o" :db/cardinality :db.cardinality/one]
            [:db/add "m" :db/ident :foo/many]
            [:db/add "m" :db/valueType :db.type/string]
            [:db/add "m" :db/cardinality :db.cardinality/many]
            [:db/add "r" :db/ident :foo/ref]
            [:db/add "r" :db/valueType :db.type/ref]
            [:db/add "r" :db/cardinality :db.cardinality/one]
        ]"#,
        )
        .unwrap();

        let mut in_progress = conn
            .begin_transaction(&mut sqlite)
            .expect("begun successfully");

        // Scoped borrow of in_progress.
        {
            let mut builder = TermBuilder::new();
            let e_x = builder.named_tempid("x");
            let e_y = builder.named_tempid("y");
            let a_ref = in_progress.get_entid(&foo_ref).expect(":foo/ref");
            let a_one = in_progress.get_entid(&foo_one).expect(":foo/one");
            let a_many = in_progress.get_entid(&foo_many).expect(":foo/many");
            let v_many_1 = TypedValue::typed_string("Some text");
            let v_many_2 = TypedValue::typed_string("Other text");
            let v_long: TypedValue = 123.into();

            builder
                .add(e_x.clone(), a_many, v_many_1)
                .expect("add succeeded");
            builder
                .add(e_x.clone(), a_many, v_many_2)
                .expect("add succeeded");
            builder.add(e_y, a_ref, e_x.clone()).expect("add succeeded");
            builder.add(e_x, a_one, v_long).expect("add succeeded");

            let (terms, tempids) = builder.build().expect("build succeeded");

            assert_eq!(tempids.len(), 2);
            assert_eq!(terms.len(), 4);

            report = in_progress.transact_entities(terms).expect("add succeeded");
            let x = report.tempids.get("x").expect("our tempid has an ID");
            let y = report.tempids.get("y").expect("our tempid has an ID");
            assert_eq!(
                in_progress
                    .lookup_value_for_attribute(*y, &foo_ref)
                    .expect("lookup succeeded"),
                Some(TypedValue::Ref(*x))
            );
            assert_eq!(
                in_progress
                    .lookup_value_for_attribute(*x, &foo_one)
                    .expect("lookup succeeded"),
                Some(TypedValue::Long(123))
            );
        }

        in_progress.commit().expect("commit succeeded");
    }

    // It's all still there after the commit.
    let x = report.tempids.get("x").expect("our tempid has an ID");
    let y = report.tempids.get("y").expect("our tempid has an ID");
    assert_eq!(
        conn.lookup_value_for_attribute(&sqlite, *y, &foo_ref)
            .expect("lookup succeeded"),
        Some(TypedValue::Ref(*x))
    );
}