// Copyright 2016 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 lazy_static; #[macro_use] extern crate mentat; extern crate mentat_core; extern crate mentat_db; extern crate rusqlite; use mentat::vocabulary; use mentat::vocabulary::{ VersionedStore, VocabularyCheck, VocabularyOutcome, }; use mentat::query::IntoResult; use mentat_core::{ HasSchema, }; // To check our working. use mentat_db::AttributeValidation; use mentat::{ Conn, NamespacedKeyword, Queryable, TypedValue, ValueType, }; use mentat::entity_builder::BuildTerms; use mentat::errors::{ Error, ErrorKind, }; lazy_static! { static ref FOO_NAME: NamespacedKeyword = { kw!(:foo/name) }; static ref FOO_MOMENT: NamespacedKeyword = { kw!(:foo/moment) }; static ref FOO_VOCAB: vocabulary::Definition = { vocabulary::Definition { name: kw!(:org.mozilla/foo), version: 1, attributes: vec![ (FOO_NAME.clone(), vocabulary::AttributeBuilder::new() .value_type(ValueType::String) .multival(false) .unique(vocabulary::attribute::Unique::Identity) .build()), (FOO_MOMENT.clone(), vocabulary::AttributeBuilder::new() .value_type(ValueType::Instant) .multival(false) .index(true) .build()), ] } }; } // Do some work with the appropriate level of paranoia for a shared system. fn be_paranoid(conn: &mut Conn, sqlite: &mut rusqlite::Connection, name: TypedValue, moment: TypedValue) { let mut in_progress = conn.begin_transaction(sqlite).expect("begun successfully"); assert!(in_progress.verify_core_schema().is_ok()); assert!(in_progress.ensure_vocabulary(&FOO_VOCAB).is_ok()); let a_moment = in_progress.attribute_for_ident(&FOO_MOMENT).expect("exists").1; let a_name = in_progress.attribute_for_ident(&FOO_NAME).expect("exists").1; let builder = in_progress.builder(); let mut entity = builder.describe_tempid("s"); entity.add(a_name, name).expect("added"); entity.add(a_moment, moment).expect("added"); assert!(entity.commit().is_ok()); // Discard the TxReport. } #[test] fn test_real_world() { let mut sqlite = mentat_db::db::new_connection("").unwrap(); let mut conn = Conn::connect(&mut sqlite).unwrap(); let alice: TypedValue = TypedValue::typed_string("Alice"); let barbara: TypedValue = TypedValue::typed_string("Barbara"); let now: TypedValue = TypedValue::current_instant(); be_paranoid(&mut conn, &mut sqlite, alice.clone(), now.clone()); be_paranoid(&mut conn, &mut sqlite, barbara.clone(), now.clone()); let results = conn.q_once(&mut sqlite, r#"[:find ?name ?when :order (asc ?name) :where [?x :foo/name ?name] [?x :foo/moment ?when] ]"#, None) .into_rel_result() .expect("query succeeded"); assert_eq!(results, vec![vec![alice, now.clone()], vec![barbara, now.clone()]]); } #[test] fn test_default_attributebuilder_complains() { // ::new is helpful. ::default is not. assert!(vocabulary::AttributeBuilder::default() .value_type(ValueType::String) .multival(true) .fulltext(true) .build() .validate(|| "Foo".to_string()) .is_err()); assert!(vocabulary::AttributeBuilder::new() .value_type(ValueType::String) .multival(true) .fulltext(true) .build() .validate(|| "Foo".to_string()) .is_ok()); } #[test] fn test_add_vocab() { let bar = vocabulary::AttributeBuilder::new() .value_type(ValueType::Instant) .multival(false) .index(true) .build(); let baz = vocabulary::AttributeBuilder::new() .value_type(ValueType::String) .multival(true) .fulltext(true) .build(); let bar_only = vec![ (kw!(:foo/bar), bar.clone()), ]; let baz_only = vec![ (kw!(:foo/baz), baz.clone()), ]; let bar_and_baz = vec![ (kw!(:foo/bar), bar.clone()), (kw!(:foo/baz), baz.clone()), ]; let foo_v1_a = vocabulary::Definition { name: kw!(:org.mozilla/foo), version: 1, attributes: bar_only.clone(), }; let foo_v1_b = vocabulary::Definition { name: kw!(:org.mozilla/foo), version: 1, attributes: bar_and_baz.clone(), }; let mut sqlite = mentat_db::db::new_connection("").unwrap(); let mut conn = Conn::connect(&mut sqlite).unwrap(); let foo_version_query = r#"[:find [?version ?aa] :where [:org.mozilla/foo :db.schema/version ?version] [:org.mozilla/foo :db.schema/attribute ?a] [?a :db/ident ?aa]]"#; let foo_attributes_query = r#"[:find [?aa ...] :where [:org.mozilla/foo :db.schema/attribute ?a] [?a :db/ident ?aa]]"#; // Scoped borrow of `conn`. { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); assert!(in_progress.verify_core_schema().is_ok()); assert_eq!(VocabularyCheck::NotPresent, in_progress.check_vocabulary(&foo_v1_a).expect("check completed")); assert_eq!(VocabularyCheck::NotPresent, in_progress.check_vocabulary(&foo_v1_b).expect("check completed")); // If we install v1.a, then it will succeed. assert_eq!(VocabularyOutcome::Installed, in_progress.ensure_vocabulary(&foo_v1_a).expect("ensure succeeded")); // Now we can query to get the vocab. let ver_attr = in_progress.q_once(foo_version_query, None) .into_tuple_result() .expect("query returns") .expect("a result"); assert_eq!(ver_attr[0], TypedValue::Long(1)); assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar")); // If we commit, it'll stick around. in_progress.commit().expect("commit succeeded"); } // It's still there. let ver_attr = conn.q_once(&mut sqlite, foo_version_query, None) .into_tuple_result() .expect("query returns") .expect("a result"); assert_eq!(ver_attr[0], TypedValue::Long(1)); assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar")); // Scoped borrow of `conn`. { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); // Subsequently ensuring v1.a again will succeed with no work done. assert_eq!(VocabularyCheck::Present, in_progress.check_vocabulary(&foo_v1_a).expect("check completed")); // Checking for v1.b will say that we have work to do. assert_eq!(VocabularyCheck::PresentButMissingAttributes { attributes: vec![&baz_only[0]], }, in_progress.check_vocabulary(&foo_v1_b).expect("check completed")); // Ensuring v1.b will succeed. assert_eq!(VocabularyOutcome::InstalledMissingAttributes, in_progress.ensure_vocabulary(&foo_v1_b).expect("ensure succeeded")); // Checking v1.a or v1.b again will still succeed with no work done. assert_eq!(VocabularyCheck::Present, in_progress.check_vocabulary(&foo_v1_a).expect("check completed")); assert_eq!(VocabularyCheck::Present, in_progress.check_vocabulary(&foo_v1_b).expect("check completed")); // Ensuring again does nothing. assert_eq!(VocabularyOutcome::Existed, in_progress.ensure_vocabulary(&foo_v1_b).expect("ensure succeeded")); in_progress.commit().expect("commit succeeded"); } // We have both attributes. let actual_attributes = conn.q_once(&mut sqlite, foo_attributes_query, None) .into_coll_result() .expect("query returns"); assert_eq!(actual_attributes, vec![ TypedValue::typed_ns_keyword("foo", "bar"), TypedValue::typed_ns_keyword("foo", "baz"), ]); // Now let's modify our vocabulary without bumping the version. This is invalid and will result // in an error. let malformed_baz = vocabulary::AttributeBuilder::default() .value_type(ValueType::Instant) .multival(true) .build(); let bar_and_malformed_baz = vec![ (kw!(:foo/bar), bar), (kw!(:foo/baz), malformed_baz.clone()), ]; let foo_v1_malformed = vocabulary::Definition { name: kw!(:org.mozilla/foo), version: 1, attributes: bar_and_malformed_baz.clone(), }; // Scoped borrow of `conn`. { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); match in_progress.ensure_vocabulary(&foo_v1_malformed) { Result::Err(Error(ErrorKind::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours), _)) => { assert_eq!(vocab.as_str(), ":org.mozilla/foo"); assert_eq!(attr.as_str(), ":foo/baz"); assert_eq!(version, 1); assert_eq!(&theirs, &baz); assert_eq!(&ours, &malformed_baz); }, _ => panic!(), } } }