Procházet zdrojové kódy

Moved integration tests out of main source tree.

Kestrel před 7 měsíci
rodič
revize
9ed2328714

+ 0 - 2
microrm/src/schema.rs

@@ -36,8 +36,6 @@ pub mod index;
 mod build;
 mod collect;
 pub(crate) mod meta;
-#[cfg(test)]
-mod tests;
 
 // ----------------------------------------------------------------------
 // API types

+ 0 - 1048
microrm/src/schema/tests.rs

@@ -1,1048 +0,0 @@
-#![allow(unused)]
-
-fn open_test_db<DB: super::Database>(identifier: &'static str) -> DB {
-    let path = format!("/tmp/microrm-{identifier}.db");
-    let _ = std::fs::remove_file(path.as_str());
-    DB::open_path(path).expect("couldn't open database file")
-}
-
-mod manual_test_db {
-    // simple hand-built database example
-    use crate::db::{Connection, StatementContext, StatementRow};
-    use crate::schema::datum::{ConcreteDatum, Datum};
-    use crate::schema::entity::{
-        Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor,
-    };
-    use crate::schema::{Database, DatabaseItem, DatabaseItemVisitor, IDMap};
-    use test_log::test;
-
-    #[derive(Debug)]
-    struct SimpleEntity {
-        name: String,
-    }
-
-    #[derive(Clone, Copy, Default, PartialEq, PartialOrd, Hash, Debug)]
-    struct SimpleEntityID(i64);
-
-    impl Datum for SimpleEntityID {
-        fn sql_type() -> &'static str {
-            "int"
-        }
-        fn accept_entity_visitor(_: &mut impl EntityVisitor) {}
-        fn accept_discriminator(d: &mut impl crate::schema::DatumDiscriminator)
-        where
-            Self: Sized,
-        {
-            d.visit_entity_id::<<Self as EntityID>::Entity>();
-        }
-
-        fn bind_to<'a>(&self, _stmt: &mut StatementContext<'a>, index: i32) {
-            todo!()
-        }
-        fn build_from<'a>(
-            rdata: crate::schema::relation::RelationData,
-            stmt: &mut StatementRow,
-            index: &mut i32,
-        ) -> crate::DBResult<Self>
-        where
-            Self: Sized,
-        {
-            todo!()
-        }
-    }
-    impl ConcreteDatum for SimpleEntityID {}
-
-    impl EntityID for SimpleEntityID {
-        type Entity = SimpleEntity;
-
-        fn from_raw(id: i64) -> Self {
-            Self(id)
-        }
-        fn into_raw(self) -> i64 {
-            self.0
-        }
-    }
-
-    impl EntityPart for SimpleEntityID {
-        type Datum = Self;
-        type Entity = SimpleEntity;
-
-        fn unique() -> bool {
-            true
-        }
-        fn part_name() -> &'static str {
-            "id"
-        }
-        fn desc() -> Option<&'static str> {
-            None
-        }
-
-        fn get_datum(from: &Self::Entity) -> &Self::Datum {
-            unreachable!()
-        }
-    }
-
-    #[derive(Clone, Default)]
-    struct SimpleEntityName;
-    impl EntityPart for SimpleEntityName {
-        type Datum = String;
-        type Entity = SimpleEntity;
-        fn part_name() -> &'static str {
-            "name"
-        }
-        fn unique() -> bool {
-            true
-        }
-        fn desc() -> Option<&'static str> {
-            None
-        }
-
-        fn get_datum(from: &Self::Entity) -> &Self::Datum {
-            &from.name
-        }
-    }
-
-    impl Entity for SimpleEntity {
-        type Parts = SimpleEntityName;
-        type Keys = SimpleEntityName;
-        type ID = SimpleEntityID;
-
-        fn build(name: String) -> Self {
-            Self { name }
-        }
-
-        fn entity_name() -> &'static str {
-            "simple_entity"
-        }
-        fn accept_part_visitor(visitor: &mut impl EntityPartVisitor<Entity = Self>) {
-            visitor.visit::<SimpleEntityName>();
-        }
-        fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
-            visitor.visit_datum::<SimpleEntityName>(&self.name);
-        }
-        fn accept_part_visitor_mut(&mut self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
-            visitor.visit_datum_mut::<SimpleEntityName>(&mut self.name);
-        }
-    }
-
-    struct SimpleDatabase {
-        strings: IDMap<SimpleEntity>,
-    }
-
-    impl Database for SimpleDatabase {
-        fn build(conn: Connection) -> Self
-        where
-            Self: Sized,
-        {
-            Self {
-                strings: IDMap::build(conn),
-            }
-        }
-
-        fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor)
-        where
-            Self: Sized,
-        {
-            <IDMap<SimpleEntity> as DatabaseItem>::accept_item_visitor(visitor);
-        }
-    }
-
-    #[test]
-    fn part_visitor() {
-        struct V<E: Entity> {
-            v: Vec<std::any::TypeId>,
-            _ghost: std::marker::PhantomData<E>,
-        }
-        impl<E: Entity> EntityPartVisitor for V<E> {
-            type Entity = E;
-            fn visit<EP: EntityPart>(&mut self) {
-                self.v.push(std::any::TypeId::of::<EP>());
-            }
-        }
-
-        let mut vis = V {
-            v: vec![],
-            _ghost: Default::default(),
-        };
-        SimpleEntity::accept_part_visitor(&mut vis);
-        assert_eq!(
-            vis.v.as_slice(),
-            &[std::any::TypeId::of::<SimpleEntityName>()]
-        );
-    }
-}
-
-mod derive_tests {
-    #![allow(unused)]
-
-    use crate::prelude::*;
-    use crate::schema::{Database, IDMap};
-    use microrm_macros::{Database, Entity};
-    use test_log::test;
-
-    #[derive(Entity)]
-    struct Role {
-        title: String,
-        permissions: String,
-    }
-
-    #[derive(Entity)]
-    struct Person {
-        #[key]
-        name: String,
-        roles: microrm::RelationMap<Role>,
-    }
-
-    #[derive(Database)]
-    struct PeopleDB {
-        people: IDMap<Person>,
-    }
-
-    #[test]
-    fn collect_test() {
-        microrm::schema::build::collect_from_database::<PeopleDB>();
-    }
-
-    #[test]
-    fn open_test() {
-        PeopleDB::open_path(":memory:");
-    }
-
-    #[test]
-    fn check_for_inserted() {
-        std::fs::remove_file("/tmp/schema.db").ok();
-        let db = PeopleDB::open_path("/tmp/schema.db").expect("couldn't open database");
-        // let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
-        let name_string = "name_here".to_string();
-        // check that it isn't in the database before we insert it
-        assert!(db.people.keyed(&name_string).get().ok().flatten().is_none());
-
-        db.people
-            .insert(Person {
-                name: name_string.clone(),
-                roles: Default::default(),
-            })
-            .expect("failed to insert");
-
-        // check that it is in the database after we insert it
-        assert!(db.people.keyed(&name_string).get().ok().flatten().is_some());
-    }
-
-    #[test]
-    fn check_relation_query_construction() {
-        let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
-
-        let name_string = "name_here".to_string();
-        db.people
-            .insert(Person {
-                name: name_string.clone(),
-                roles: Default::default(),
-            })
-            .expect("couldn't insert test person");
-
-        let person = db
-            .people
-            .keyed(&name_string)
-            .get()
-            .ok()
-            .flatten()
-            .expect("couldn't re-get test person entity");
-
-        person
-            .roles
-            .get()
-            .expect("couldn't get related role entity");
-    }
-
-    #[test]
-    fn check_relation_insertion() {
-        std::fs::remove_file("/tmp/insert.db").ok();
-        let db = PeopleDB::open_path("/tmp/insert.db").expect("couldn't open database");
-        // let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
-
-        let name_string = "name_here".to_string();
-        db.people
-            .insert(Person {
-                name: name_string.clone(),
-                roles: Default::default(),
-            })
-            .expect("couldn't insert test person");
-
-        let person = db
-            .people
-            .keyed(&name_string)
-            .get()
-            .ok()
-            .flatten()
-            .expect("couldn't re-get test person entity");
-
-        person.roles.insert(Role {
-            title: "title A".to_string(),
-            permissions: "permissions A".to_string(),
-        });
-    }
-
-    #[test]
-    fn delete_test() {
-        let db = PeopleDB::open_path(":memory:").expect("couldn't open test db");
-
-        let id = db
-            .people
-            .insert(Person {
-                name: "person_name".to_string(),
-                roles: Default::default(),
-            })
-            .expect("couldn't insert test person");
-        assert!(db.people.by_id(id).expect("couldn't query db").is_some());
-
-        db.people.with(id, &id).delete();
-
-        assert!(db.people.by_id(id).expect("couldn't query db").is_none());
-    }
-
-    #[test]
-    fn update_test() {
-        let db = PeopleDB::open_path(":memory:").expect("couldn't open test db");
-
-        let mut stored = db
-            .people
-            .insert_and_return(Person {
-                name: "person_name".to_string(),
-                roles: Default::default(),
-            })
-            .expect("couldn't insert test person");
-
-        assert_eq!(
-            db.people
-                .with(Person::Name, "person_name")
-                .count()
-                .expect("couldn't execute count query"),
-            1
-        );
-
-        stored.name = "another_person_name".into();
-        stored.sync();
-
-        assert_eq!(
-            db.people
-                .with(Person::Name, "person_name")
-                .count()
-                .expect("couldn't execute count query"),
-            0
-        );
-    }
-}
-
-mod mutual_relationship {
-    use super::open_test_db;
-    use crate::prelude::*;
-    use microrm_macros::{Database, Entity};
-    use test_log::test;
-
-    struct CR;
-    impl Relation for CR {
-        type Domain = Customer;
-        type Range = Receipt;
-
-        const NAME: &'static str = "CR";
-    }
-
-    #[derive(Entity)]
-    struct Customer {
-        name: String,
-        receipts: microrm::RelationDomain<CR>,
-    }
-
-    #[derive(Entity)]
-    struct Receipt {
-        value: usize,
-        customers: microrm::RelationRange<CR>,
-    }
-
-    #[derive(Database)]
-    struct ReceiptDB {
-        customers: microrm::IDMap<Customer>,
-        receipts: microrm::IDMap<Receipt>,
-    }
-
-    #[test]
-    fn check_schema_creation() {
-        let db = open_test_db::<ReceiptDB>("mutual_relationship_create");
-    }
-
-    #[test]
-    fn simple_operations() {
-        let db = open_test_db::<ReceiptDB>("mutual_relationship_simple");
-
-        let ca = db
-            .customers
-            .insert(Customer {
-                name: "customer A".to_string(),
-                receipts: Default::default(),
-            })
-            .expect("couldn't insert customer record");
-
-        let ra = db
-            .receipts
-            .insert(Receipt {
-                value: 32usize,
-                customers: Default::default(),
-            })
-            .expect("couldn't insert receipt record");
-
-        let rb = db
-            .receipts
-            .insert(Receipt {
-                value: 64usize,
-                customers: Default::default(),
-            })
-            .expect("couldn't insert receipt record");
-
-        let e_ca = db
-            .customers
-            .by_id(ca)
-            .expect("couldn't retrieve customer record")
-            .expect("no customer record");
-
-        println!(
-            "connecting customer a (ID {:?}) to receipt a (ID {:?})",
-            ca, ra
-        );
-        e_ca.receipts
-            .connect_to(ra)
-            .expect("couldn't relationiate customer with receipt a");
-        println!(
-            "connecting customer a (ID {:?}) to receipt b (ID {:?})",
-            ca, rb
-        );
-        e_ca.receipts
-            .connect_to(rb)
-            .expect("couldn't relationiate customer with receipt b");
-
-        println!("connected!");
-
-        // technically this can fail if sqlite gives ra and rb back in the opposite order, which is
-        // valid behaviour
-        assert_eq!(
-            e_ca.receipts
-                .get()
-                .expect("couldn't get receipts related with customer")
-                .into_iter()
-                .map(|x| x.id())
-                .collect::<Vec<_>>(),
-            vec![ra, rb]
-        );
-
-        // check that the reverse direction was also added
-        let e_ra = db
-            .receipts
-            .by_id(ra)
-            .expect("couldn't retreieve receipt record")
-            .expect("no receipt record");
-
-        assert_eq!(
-            e_ra.customers
-                .get()
-                .expect("couldn't get related customers")
-                .into_iter()
-                .map(|x| x.id())
-                .collect::<Vec<_>>(),
-            vec![ca]
-        );
-    }
-}
-
-mod reserved_words {
-    use crate::db::Connection;
-    use crate::prelude::*;
-    use crate::schema::entity::Entity;
-    use test_log::test;
-
-    #[derive(Entity)]
-    struct Select {
-        delete: String,
-    }
-
-    #[derive(Entity)]
-    struct Group {
-        by: microrm::RelationMap<Select>,
-    }
-
-    #[derive(Database)]
-    struct ReservedWordDB {
-        group: microrm::IDMap<Group>,
-    }
-
-    #[test]
-    fn open_test() {
-        ReservedWordDB::open_path(":memory:");
-    }
-}
-
-mod join_test {
-    use super::open_test_db;
-    use crate::prelude::*;
-    use crate::schema;
-    use test_log::test;
-
-    #[derive(Default, Entity)]
-    struct Base {
-        name: String,
-        targets: microrm::RelationMap<Target>,
-    }
-
-    #[derive(Default, Entity)]
-    struct Target {
-        name: String,
-        indirect_targets: microrm::RelationMap<IndirectTarget>,
-    }
-
-    #[derive(Default, Entity)]
-    struct IndirectTarget {
-        name: String,
-    }
-
-    #[derive(Database)]
-    struct JoinDB {
-        bases: microrm::IDMap<Base>,
-        targets: microrm::IDMap<Target>,
-        indirect: microrm::IDMap<IndirectTarget>,
-    }
-
-    #[test]
-    fn simple_join() {
-        let db = open_test_db::<JoinDB>("simple_join_test");
-
-        let b1id = db
-            .bases
-            .insert(Base {
-                name: "base1".to_string(),
-                targets: Default::default(),
-            })
-            .expect("couldn't insert base");
-        let b2id = db
-            .bases
-            .insert(Base {
-                name: "base2".to_string(),
-                targets: Default::default(),
-            })
-            .expect("couldn't insert base");
-        let b3id = db
-            .bases
-            .insert(Base {
-                name: "base3".to_string(),
-                targets: Default::default(),
-            })
-            .expect("couldn't insert base");
-
-        let t1id = db
-            .targets
-            .insert(Target {
-                name: "target1".to_string(),
-                ..Default::default()
-            })
-            .expect("couldn't insert target");
-        let t2id = db
-            .targets
-            .insert(Target {
-                name: "target2".to_string(),
-                ..Default::default()
-            })
-            .expect("couldn't insert target");
-        let t3id = db
-            .targets
-            .insert(Target {
-                name: "target3".to_string(),
-                ..Default::default()
-            })
-            .expect("couldn't insert target");
-
-        let it1id = db
-            .indirect
-            .insert(IndirectTarget {
-                name: "itarget1".to_string(),
-                ..Default::default()
-            })
-            .expect("couldn't insert target");
-        let it2id = db
-            .indirect
-            .insert(IndirectTarget {
-                name: "itarget2".to_string(),
-                ..Default::default()
-            })
-            .expect("couldn't insert target");
-        let it3id = db
-            .indirect
-            .insert(IndirectTarget {
-                name: "itarget3".to_string(),
-                ..Default::default()
-            })
-            .expect("couldn't insert target");
-
-        log::trace!("looking up base by ID {:?}", b1id);
-
-        let b1 = db
-            .bases
-            .by_id(b1id)
-            .expect("couldn't get base")
-            .expect("couldn't get base");
-        b1.targets
-            .connect_to(t1id)
-            .expect("couldn't connect b1 to t1id");
-        b1.targets
-            .connect_to(t2id)
-            .expect("couldn't connect b1 to t2id");
-
-        let b2 = db
-            .bases
-            .by_id(b2id)
-            .expect("couldn't get base")
-            .expect("couldn't get base");
-        b2.targets
-            .connect_to(t2id)
-            .expect("couldn't connect b2 to t2id");
-        b2.targets
-            .connect_to(t3id)
-            .expect("couldn't connect b2 to t3id");
-
-        let t1 = db
-            .targets
-            .by_id(t2id)
-            .expect("couldn't get target")
-            .expect("couldn't get target");
-        t1.indirect_targets
-            .connect_to(it1id)
-            .expect("couldn't connect t1 to it1id");
-
-        assert_eq!(
-            db.bases
-                .join(Base::Targets)
-                .get()
-                .expect("couldn't get joined results")
-                .len(),
-            3
-        );
-
-        let double_join = db
-            .bases
-            .join(Base::Targets)
-            .join(Target::IndirectTargets)
-            .get()
-            .expect("couldn't get double-joined results");
-        assert_eq!(double_join.len(), 1);
-
-        let double_join_count = db
-            .bases
-            .join(Base::Targets)
-            .join(Target::IndirectTargets)
-            .count()
-            .expect("couldn't count double-joined results");
-        assert_eq!(double_join_count, 1);
-    }
-}
-
-mod query_equivalence {
-    use crate::prelude::*;
-    use test_log::test;
-    #[derive(Entity)]
-    struct SingleItem {
-        #[key]
-        s: String,
-        v: Vec<u8>,
-    }
-
-    #[derive(Entity)]
-    struct DoubleItem {
-        #[key]
-        s: String,
-        #[key]
-        t: String,
-        v: usize,
-    }
-
-    #[derive(Database)]
-    struct ItemDB {
-        single_items: microrm::IDMap<SingleItem>,
-        double_items: microrm::IDMap<DoubleItem>,
-    }
-
-    #[test]
-    fn single_test() {
-        let db = ItemDB::open_path(":memory:").expect("couldn't open test db");
-
-        db.single_items
-            .insert(SingleItem {
-                s: "string 1".into(),
-                v: vec![0u8, 1u8, 2u8],
-            })
-            .expect("couldn't insert test item");
-
-        db.single_items
-            .insert(SingleItem {
-                s: "string 2".into(),
-                v: vec![0u8, 1u8, 2u8],
-            })
-            .expect("couldn't insert test item");
-
-        assert!(db
-            .single_items
-            .keyed("string 2")
-            .get()
-            .expect("couldn't query db")
-            .is_some());
-        assert!(db
-            .single_items
-            .keyed("string 3")
-            .get()
-            .expect("couldn't query db")
-            .is_none());
-
-        assert!(db
-            .single_items
-            .keyed(String::from("string 2"))
-            .get()
-            .expect("couldn't query db")
-            .is_some());
-        assert!(db
-            .single_items
-            .keyed(String::from("string 3"))
-            .get()
-            .expect("couldn't query db")
-            .is_none());
-    }
-
-    #[test]
-    fn double_test() {
-        let db = ItemDB::open_path(":memory:").expect("couldn't open test db");
-
-        db.double_items
-            .insert(DoubleItem {
-                s: "string 1".into(),
-                t: "second string 1".into(),
-                v: 42,
-            })
-            .expect("couldn't insert test item");
-
-        db.double_items
-            .insert(DoubleItem {
-                s: "string 2".into(),
-                t: "second string 2".into(),
-                v: 6,
-            })
-            .expect("couldn't insert test item");
-
-        assert!(db
-            .double_items
-            .keyed(("string 2", "second string 2"))
-            .get()
-            .expect("couldn't query db")
-            .is_some());
-        assert!(db
-            .double_items
-            .keyed(("string 3", "second string 3"))
-            .get()
-            .expect("couldn't query db")
-            .is_none());
-
-        assert!(db
-            .double_items
-            .keyed((String::from("string 2"), String::from("second string 2")))
-            .get()
-            .expect("couldn't query db")
-            .is_some());
-        assert!(db
-            .double_items
-            .keyed((String::from("string 3"), String::from("second string 3")))
-            .get()
-            .expect("couldn't query db")
-            .is_none());
-    }
-
-    #[test]
-    fn with_test() {
-        let db = ItemDB::open_path(":memory:").expect("couldn't open test db");
-
-        db.single_items
-            .insert(SingleItem {
-                s: "string 1".into(),
-                v: vec![0u8, 1u8, 2u8],
-            })
-            .expect("couldn't insert test item");
-
-        db.single_items
-            .insert(SingleItem {
-                s: "string 2".into(),
-                v: vec![0u8, 1u8, 2u8],
-            })
-            .expect("couldn't insert test item");
-
-        assert_eq!(
-            db.single_items
-                .with(SingleItem::V, [0u8].as_slice())
-                .get()
-                .expect("couldn't query db")
-                .len(),
-            0
-        );
-        assert_eq!(
-            db.single_items
-                .with(SingleItem::V, [0u8, 1, 2].as_slice())
-                .get()
-                .expect("couldn't query db")
-                .len(),
-            2
-        );
-    }
-}
-
-mod injective_test {
-    use microrm::prelude::*;
-    use test_log::test;
-
-    struct AuthorBookRelation;
-    impl microrm::Relation for AuthorBookRelation {
-        type Domain = Author;
-        type Range = Book;
-
-        const NAME: &'static str = "AuthorBook";
-        const INJECTIVE: bool = true;
-    }
-
-    #[derive(Entity)]
-    struct Author {
-        #[key]
-        name: String,
-
-        works: microrm::RelationDomain<AuthorBookRelation>,
-    }
-
-    #[derive(Entity)]
-    struct Book {
-        #[key]
-        title: String,
-
-        creator: microrm::RelationRange<AuthorBookRelation>,
-    }
-
-    #[derive(Database)]
-    struct ABDB {
-        authors: microrm::IDMap<Author>,
-        books: microrm::IDMap<Book>,
-    }
-
-    #[test]
-    fn schema_creation_test() {
-        let db = ABDB::open_path(":memory:").expect("couldn't open in-memory database");
-    }
-
-    #[test]
-    fn single_connection() {
-        let db = ABDB::open_path(":memory:").expect("couldn't open in-memory database");
-
-        let a1 = db
-            .authors
-            .insert_and_return(Author {
-                name: "Homer".to_string(),
-                works: Default::default(),
-            })
-            .expect("couldn't insert author");
-
-        let a2 = db
-            .authors
-            .insert_and_return(Author {
-                name: "Virgil".to_string(),
-                works: Default::default(),
-            })
-            .expect("couldn't insert author");
-
-        let b1_id = db
-            .books
-            .insert(Book {
-                title: "Odyssey".to_string(),
-                creator: Default::default(),
-            })
-            .expect("couldn't insert book");
-        let b2_id = db
-            .books
-            .insert(Book {
-                title: "Aeneid".to_string(),
-                creator: Default::default(),
-            })
-            .expect("couldn't insert book");
-
-        a1.works
-            .connect_to(b1_id)
-            .expect("couldn't connect a1 and b1");
-        a2.works
-            .connect_to(b2_id)
-            .expect("couldn't connect a2 and b2");
-
-        // we can't claim that Homer wrote the Aeneid because it's an injective relationship and
-        // in this model, only one Author can claim the Book as their work
-        match a1.works.connect_to(b2_id) {
-            Err(microrm::Error::ConstraintViolation(_)) => {
-                // all good
-            }
-            Err(_) => {
-                panic!("Unexpected error while testing injective connection");
-            }
-            Ok(_) => {
-                panic!("Unexpected success while testing injective connection");
-            }
-        }
-    }
-}
-
-mod index_test {
-    use microrm::prelude::*;
-    use microrm_macros::index_cols;
-    use test_log::test;
-
-    use microrm::schema::index::Index;
-
-    #[derive(Entity)]
-    struct TwoWay {
-        up: String,
-        left: String,
-        right: String,
-    }
-
-    #[derive(Database)]
-    struct TWDB {
-        entries: microrm::IDMap<TwoWay>,
-        left_index: Index<TwoWay, index_cols![TwoWay::left]>,
-        right_index: Index<TwoWay, index_cols![TwoWay::right]>,
-    }
-
-    #[test]
-    fn db_construction() {
-        let db = TWDB::open_path(":memory:").expect("couldn't open in-memory db");
-    }
-
-    #[test]
-    fn lookup_test() {
-        let db = TWDB::open_path(":memory:").expect("couldn't open in-memory db");
-
-        let e1 = db
-            .entries
-            .insert_and_return(TwoWay {
-                up: "up1".into(),
-                left: "left1".into(),
-                right: "right1".into(),
-            })
-            .expect("couldn't insert entry");
-        let e2 = db
-            .entries
-            .insert_and_return(TwoWay {
-                up: "up2".into(),
-                left: "left2".into(),
-                right: "right2".into(),
-            })
-            .expect("couldn't insert entry");
-
-        assert!(db
-            .entries
-            .indexed(&db.left_index, "left3")
-            .get()
-            .expect("couldn't query left index")
-            .is_none());
-        assert_eq!(
-            db.entries
-                .indexed(&db.left_index, "left2")
-                .get()
-                .expect("couldn't query left index")
-                .as_ref(),
-            Some(&e2)
-        );
-        assert_eq!(
-            db.entries
-                .indexed(&db.left_index, "left1")
-                .get()
-                .expect("couldn't query left index")
-                .as_ref(),
-            Some(&e1)
-        );
-
-        assert!(db
-            .entries
-            .indexed(&db.right_index, "right3")
-            .get()
-            .expect("couldn't query right index")
-            .is_none());
-        assert_eq!(
-            db.entries
-                .indexed(&db.right_index, "right2")
-                .get()
-                .expect("couldn't query right index")
-                .as_ref(),
-            Some(&e2)
-        );
-        assert_eq!(
-            db.entries
-                .indexed(&db.right_index, "right1")
-                .get()
-                .expect("couldn't query right index")
-                .as_ref(),
-            Some(&e1)
-        );
-    }
-}
-
-mod foreign_key {
-    use microrm::prelude::*;
-    use test_log::test;
-
-    #[derive(Entity)]
-    struct Child {
-        #[key]
-        name: String,
-        parent: ParentID,
-    }
-
-    #[derive(Entity)]
-    struct Parent {
-        name: String,
-    }
-
-    #[derive(Database)]
-    struct PeopleDB {
-        children: microrm::IDMap<Child>,
-        parents: microrm::IDMap<Parent>,
-    }
-
-    #[test]
-    fn simple_fk_query() {
-        let db = PeopleDB::open_path(":memory:").expect("couldn't open DB");
-
-        let pid = db
-            .parents
-            .insert(Parent {
-                name: "parent1".to_string(),
-            })
-            .unwrap();
-        let c1id = db
-            .children
-            .insert(Child {
-                name: "child1".to_string(),
-                parent: pid,
-            })
-            .unwrap();
-
-        assert_eq!(
-            db.children
-                .foreign(Child::Parent)
-                .get_ids()
-                .expect("couldn't run query"),
-            vec![pid]
-        );
-
-        assert_eq!(
-            db.children
-                .keyed("child1")
-                .foreign(Child::Parent)
-                .get_ids()
-                .expect("couldn't run query"),
-            Some(pid)
-        );
-    }
-}

+ 10 - 0
microrm/tests/common/mod.rs

@@ -0,0 +1,10 @@
+use microrm::prelude::*;
+
+pub fn open_test_db<DB: Database>(identifier: &'static str) -> DB {
+    let path = format!(
+        "{tmpdir}/microrm-test-{identifier}.db",
+        tmpdir = std::env!("CARGO_TARGET_TMPDIR")
+    );
+    let _ = std::fs::remove_file(path.as_str());
+    DB::open_path(path).expect("couldn't open database file")
+}

+ 150 - 0
microrm/tests/derive.rs

@@ -0,0 +1,150 @@
+#![allow(unused)]
+
+use microrm::prelude::*;
+use microrm::schema::{Database, IDMap};
+use test_log::test;
+
+mod common;
+
+#[derive(Entity)]
+struct Role {
+    title: String,
+    permissions: String,
+}
+
+#[derive(Entity)]
+struct Person {
+    #[key]
+    name: String,
+    roles: microrm::RelationMap<Role>,
+}
+
+#[derive(Database)]
+struct PeopleDB {
+    people: IDMap<Person>,
+}
+
+#[test]
+fn open_test() {
+    PeopleDB::open_path(":memory:");
+}
+
+#[test]
+fn check_for_inserted() {
+    let db = common::open_test_db::<PeopleDB>("derive_check_for_inserted");
+    let name_string = "name_here".to_string();
+    // check that it isn't in the database before we insert it
+    assert!(db.people.keyed(&name_string).get().ok().flatten().is_none());
+
+    db.people
+        .insert(Person {
+            name: name_string.clone(),
+            roles: Default::default(),
+        })
+        .expect("failed to insert");
+
+    // check that it is in the database after we insert it
+    assert!(db.people.keyed(&name_string).get().ok().flatten().is_some());
+}
+
+#[test]
+fn check_relation_query_construction() {
+    let db = common::open_test_db::<PeopleDB>("derive_check_relation_query_construction");
+
+    let name_string = "name_here".to_string();
+    db.people
+        .insert(Person {
+            name: name_string.clone(),
+            roles: Default::default(),
+        })
+        .expect("couldn't insert test person");
+
+    let person = db
+        .people
+        .keyed(&name_string)
+        .get()
+        .ok()
+        .flatten()
+        .expect("couldn't re-get test person entity");
+
+    person
+        .roles
+        .get()
+        .expect("couldn't get related role entity");
+}
+
+#[test]
+fn check_relation_insertion() {
+    let db = common::open_test_db::<PeopleDB>("derive_check_relation_insertion");
+
+    let name_string = "name_here".to_string();
+    db.people
+        .insert(Person {
+            name: name_string.clone(),
+            roles: Default::default(),
+        })
+        .expect("couldn't insert test person");
+
+    let person = db
+        .people
+        .keyed(&name_string)
+        .get()
+        .ok()
+        .flatten()
+        .expect("couldn't re-get test person entity");
+
+    person.roles.insert(Role {
+        title: "title A".to_string(),
+        permissions: "permissions A".to_string(),
+    });
+}
+
+#[test]
+fn delete_test() {
+    let db = common::open_test_db::<PeopleDB>("derive_delete_test");
+
+    let id = db
+        .people
+        .insert(Person {
+            name: "person_name".to_string(),
+            roles: Default::default(),
+        })
+        .expect("couldn't insert test person");
+    assert!(db.people.by_id(id).expect("couldn't query db").is_some());
+
+    db.people.with(id, &id).delete();
+
+    assert!(db.people.by_id(id).expect("couldn't query db").is_none());
+}
+
+#[test]
+fn update_test() {
+    let db = common::open_test_db::<PeopleDB>("derive_update_test");
+
+    let mut stored = db
+        .people
+        .insert_and_return(Person {
+            name: "person_name".to_string(),
+            roles: Default::default(),
+        })
+        .expect("couldn't insert test person");
+
+    assert_eq!(
+        db.people
+            .with(Person::Name, "person_name")
+            .count()
+            .expect("couldn't execute count query"),
+        1
+    );
+
+    stored.name = "another_person_name".into();
+    stored.sync();
+
+    assert_eq!(
+        db.people
+            .with(Person::Name, "person_name")
+            .count()
+            .expect("couldn't execute count query"),
+        0
+    );
+}

+ 58 - 0
microrm/tests/fkey.rs

@@ -0,0 +1,58 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+#[derive(Entity)]
+struct Child {
+    #[key]
+    name: String,
+    parent: ParentID,
+}
+
+#[derive(Entity)]
+struct Parent {
+    name: String,
+}
+
+#[derive(Database)]
+struct PeopleDB {
+    children: microrm::IDMap<Child>,
+    parents: microrm::IDMap<Parent>,
+}
+
+#[test]
+fn simple_query() {
+    let db = common::open_test_db::<PeopleDB>("fkey_simple_query");
+
+    let pid = db
+        .parents
+        .insert(Parent {
+            name: "parent1".to_string(),
+        })
+        .unwrap();
+    let _c1id = db
+        .children
+        .insert(Child {
+            name: "child1".to_string(),
+            parent: pid,
+        })
+        .unwrap();
+
+    assert_eq!(
+        db.children
+            .foreign(Child::Parent)
+            .get_ids()
+            .expect("couldn't run query"),
+        vec![pid]
+    );
+
+    assert_eq!(
+        db.children
+            .keyed("child1")
+            .foreign(Child::Parent)
+            .get_ids()
+            .expect("couldn't run query"),
+        Some(pid)
+    );
+}

+ 94 - 0
microrm/tests/index.rs

@@ -0,0 +1,94 @@
+use microrm::prelude::*;
+use microrm_macros::index_cols;
+use test_log::test;
+
+use microrm::schema::index::Index;
+
+mod common;
+
+#[derive(Entity)]
+struct TwoWay {
+    up: String,
+    left: String,
+    right: String,
+}
+
+#[derive(Database)]
+struct TWDB {
+    entries: microrm::IDMap<TwoWay>,
+    left_index: Index<TwoWay, index_cols![TwoWay::left]>,
+    right_index: Index<TwoWay, index_cols![TwoWay::right]>,
+}
+
+#[test]
+fn db_construction() {
+    let _db = common::open_test_db::<TWDB>("index_db_construction");
+}
+
+#[test]
+fn lookup() {
+    let db = common::open_test_db::<TWDB>("index_lookup");
+
+    let e1 = db
+        .entries
+        .insert_and_return(TwoWay {
+            up: "up1".into(),
+            left: "left1".into(),
+            right: "right1".into(),
+        })
+        .expect("couldn't insert entry");
+    let e2 = db
+        .entries
+        .insert_and_return(TwoWay {
+            up: "up2".into(),
+            left: "left2".into(),
+            right: "right2".into(),
+        })
+        .expect("couldn't insert entry");
+
+    assert!(db
+        .entries
+        .indexed(&db.left_index, "left3")
+        .get()
+        .expect("couldn't query left index")
+        .is_none());
+    assert_eq!(
+        db.entries
+            .indexed(&db.left_index, "left2")
+            .get()
+            .expect("couldn't query left index")
+            .as_ref(),
+        Some(&e2)
+    );
+    assert_eq!(
+        db.entries
+            .indexed(&db.left_index, "left1")
+            .get()
+            .expect("couldn't query left index")
+            .as_ref(),
+        Some(&e1)
+    );
+
+    assert!(db
+        .entries
+        .indexed(&db.right_index, "right3")
+        .get()
+        .expect("couldn't query right index")
+        .is_none());
+    assert_eq!(
+        db.entries
+            .indexed(&db.right_index, "right2")
+            .get()
+            .expect("couldn't query right index")
+            .as_ref(),
+        Some(&e2)
+    );
+    assert_eq!(
+        db.entries
+            .indexed(&db.right_index, "right1")
+            .get()
+            .expect("couldn't query right index")
+            .as_ref(),
+        Some(&e1)
+    );
+}

+ 97 - 0
microrm/tests/injective.rs

@@ -0,0 +1,97 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+struct AuthorBookRelation;
+impl microrm::Relation for AuthorBookRelation {
+    type Domain = Author;
+    type Range = Book;
+
+    const NAME: &'static str = "AuthorBook";
+    const INJECTIVE: bool = true;
+}
+
+#[derive(Entity)]
+struct Author {
+    #[key]
+    name: String,
+
+    works: microrm::RelationDomain<AuthorBookRelation>,
+}
+
+#[derive(Entity)]
+struct Book {
+    #[key]
+    title: String,
+
+    creator: microrm::RelationRange<AuthorBookRelation>,
+}
+
+#[derive(Database)]
+struct ABDB {
+    authors: microrm::IDMap<Author>,
+    books: microrm::IDMap<Book>,
+}
+
+#[test]
+fn schema_creation() {
+    let _db = common::open_test_db::<ABDB>("injective_schema_creation");
+}
+
+#[test]
+fn single_connection() {
+    let db = common::open_test_db::<ABDB>("injective_single_connection");
+
+    let a1 = db
+        .authors
+        .insert_and_return(Author {
+            name: "Homer".to_string(),
+            works: Default::default(),
+        })
+        .expect("couldn't insert author");
+
+    let a2 = db
+        .authors
+        .insert_and_return(Author {
+            name: "Virgil".to_string(),
+            works: Default::default(),
+        })
+        .expect("couldn't insert author");
+
+    let b1_id = db
+        .books
+        .insert(Book {
+            title: "Odyssey".to_string(),
+            creator: Default::default(),
+        })
+        .expect("couldn't insert book");
+    let b2_id = db
+        .books
+        .insert(Book {
+            title: "Aeneid".to_string(),
+            creator: Default::default(),
+        })
+        .expect("couldn't insert book");
+
+    a1.works
+        .connect_to(b1_id)
+        .expect("couldn't connect a1 and b1");
+    a2.works
+        .connect_to(b2_id)
+        .expect("couldn't connect a2 and b2");
+
+    // we can't claim that Homer wrote the Aeneid because it's an injective relationship and
+    // in this model, only one Author can claim the Book as their work
+    match a1.works.connect_to(b2_id) {
+        Err(microrm::Error::ConstraintViolation(_)) => {
+            // all good
+        }
+        Err(_) => {
+            panic!("Unexpected error while testing injective connection");
+        }
+        Ok(_) => {
+            panic!("Unexpected success while testing injective connection");
+        }
+    }
+}

+ 159 - 0
microrm/tests/join.rs

@@ -0,0 +1,159 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+#[derive(Default, Entity)]
+struct Base {
+    name: String,
+    targets: microrm::RelationMap<Target>,
+}
+
+#[derive(Default, Entity)]
+struct Target {
+    name: String,
+    indirect_targets: microrm::RelationMap<IndirectTarget>,
+}
+
+#[derive(Default, Entity)]
+struct IndirectTarget {
+    name: String,
+}
+
+#[derive(Database)]
+struct JoinDB {
+    bases: microrm::IDMap<Base>,
+    targets: microrm::IDMap<Target>,
+    indirect: microrm::IDMap<IndirectTarget>,
+}
+
+#[test]
+fn simple_ops() {
+    let db = common::open_test_db::<JoinDB>("join_simple_ops");
+
+    let b1id = db
+        .bases
+        .insert(Base {
+            name: "base1".to_string(),
+            targets: Default::default(),
+        })
+        .expect("couldn't insert base");
+    let b2id = db
+        .bases
+        .insert(Base {
+            name: "base2".to_string(),
+            targets: Default::default(),
+        })
+        .expect("couldn't insert base");
+    let _b3id = db
+        .bases
+        .insert(Base {
+            name: "base3".to_string(),
+            targets: Default::default(),
+        })
+        .expect("couldn't insert base");
+
+    let t1id = db
+        .targets
+        .insert(Target {
+            name: "target1".to_string(),
+            ..Default::default()
+        })
+        .expect("couldn't insert target");
+    let t2id = db
+        .targets
+        .insert(Target {
+            name: "target2".to_string(),
+            ..Default::default()
+        })
+        .expect("couldn't insert target");
+    let t3id = db
+        .targets
+        .insert(Target {
+            name: "target3".to_string(),
+            ..Default::default()
+        })
+        .expect("couldn't insert target");
+
+    let it1id = db
+        .indirect
+        .insert(IndirectTarget {
+            name: "itarget1".to_string(),
+            ..Default::default()
+        })
+        .expect("couldn't insert target");
+    let _it2id = db
+        .indirect
+        .insert(IndirectTarget {
+            name: "itarget2".to_string(),
+            ..Default::default()
+        })
+        .expect("couldn't insert target");
+    let _it3id = db
+        .indirect
+        .insert(IndirectTarget {
+            name: "itarget3".to_string(),
+            ..Default::default()
+        })
+        .expect("couldn't insert target");
+
+    log::trace!("looking up base by ID {:?}", b1id);
+
+    let b1 = db
+        .bases
+        .by_id(b1id)
+        .expect("couldn't get base")
+        .expect("couldn't get base");
+    b1.targets
+        .connect_to(t1id)
+        .expect("couldn't connect b1 to t1id");
+    b1.targets
+        .connect_to(t2id)
+        .expect("couldn't connect b1 to t2id");
+
+    let b2 = db
+        .bases
+        .by_id(b2id)
+        .expect("couldn't get base")
+        .expect("couldn't get base");
+    b2.targets
+        .connect_to(t2id)
+        .expect("couldn't connect b2 to t2id");
+    b2.targets
+        .connect_to(t3id)
+        .expect("couldn't connect b2 to t3id");
+
+    let t1 = db
+        .targets
+        .by_id(t2id)
+        .expect("couldn't get target")
+        .expect("couldn't get target");
+    t1.indirect_targets
+        .connect_to(it1id)
+        .expect("couldn't connect t1 to it1id");
+
+    assert_eq!(
+        db.bases
+            .join(Base::Targets)
+            .get()
+            .expect("couldn't get joined results")
+            .len(),
+        3
+    );
+
+    let double_join = db
+        .bases
+        .join(Base::Targets)
+        .join(Target::IndirectTargets)
+        .get()
+        .expect("couldn't get double-joined results");
+    assert_eq!(double_join.len(), 1);
+
+    let double_join_count = db
+        .bases
+        .join(Base::Targets)
+        .join(Target::IndirectTargets)
+        .count()
+        .expect("couldn't count double-joined results");
+    assert_eq!(double_join_count, 1);
+}

+ 163 - 0
microrm/tests/manual_construction.rs

@@ -0,0 +1,163 @@
+// simple hand-built database example
+#![allow(unused)]
+
+use microrm::db::{Connection, StatementContext, StatementRow};
+use microrm::schema::datum::{ConcreteDatum, Datum};
+use microrm::schema::entity::{Entity, EntityID, EntityPart, EntityPartVisitor, EntityVisitor};
+use microrm::schema::{Database, DatabaseItem, DatabaseItemVisitor, IDMap};
+use test_log::test;
+
+#[derive(Debug)]
+struct SimpleEntity {
+    name: String,
+}
+
+#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Hash, Debug)]
+struct SimpleEntityID(i64);
+
+impl Datum for SimpleEntityID {
+    fn sql_type() -> &'static str {
+        "int"
+    }
+    fn accept_entity_visitor(_: &mut impl EntityVisitor) {}
+    fn accept_discriminator(d: &mut impl microrm::schema::datum::DatumDiscriminator)
+    where
+        Self: Sized,
+    {
+        d.visit_entity_id::<<Self as EntityID>::Entity>();
+    }
+
+    fn bind_to<'a>(&self, _stmt: &mut StatementContext<'a>, _index: i32) {
+        todo!()
+    }
+    fn build_from<'a>(
+        _rdata: microrm::schema::relation::RelationData,
+        _stmt: &mut StatementRow,
+        _index: &mut i32,
+    ) -> microrm::DBResult<Self>
+    where
+        Self: Sized,
+    {
+        todo!()
+    }
+}
+impl ConcreteDatum for SimpleEntityID {}
+
+impl EntityID for SimpleEntityID {
+    type Entity = SimpleEntity;
+
+    fn from_raw(id: i64) -> Self {
+        Self(id)
+    }
+    fn into_raw(self) -> i64 {
+        self.0
+    }
+}
+
+impl EntityPart for SimpleEntityID {
+    type Datum = Self;
+    type Entity = SimpleEntity;
+
+    fn unique() -> bool {
+        true
+    }
+    fn part_name() -> &'static str {
+        "id"
+    }
+    fn desc() -> Option<&'static str> {
+        None
+    }
+
+    fn get_datum(_from: &Self::Entity) -> &Self::Datum {
+        unreachable!()
+    }
+}
+
+#[derive(Clone, Default)]
+struct SimpleEntityName;
+impl EntityPart for SimpleEntityName {
+    type Datum = String;
+    type Entity = SimpleEntity;
+    fn part_name() -> &'static str {
+        "name"
+    }
+    fn unique() -> bool {
+        true
+    }
+    fn desc() -> Option<&'static str> {
+        None
+    }
+
+    fn get_datum(from: &Self::Entity) -> &Self::Datum {
+        &from.name
+    }
+}
+
+impl Entity for SimpleEntity {
+    type Parts = SimpleEntityName;
+    type Keys = SimpleEntityName;
+    type ID = SimpleEntityID;
+
+    fn build(name: String) -> Self {
+        Self { name }
+    }
+
+    fn entity_name() -> &'static str {
+        "simple_entity"
+    }
+    fn accept_part_visitor(visitor: &mut impl EntityPartVisitor<Entity = Self>) {
+        visitor.visit::<SimpleEntityName>();
+    }
+    fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
+        visitor.visit_datum::<SimpleEntityName>(&self.name);
+    }
+    fn accept_part_visitor_mut(&mut self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
+        visitor.visit_datum_mut::<SimpleEntityName>(&mut self.name);
+    }
+}
+
+struct SimpleDatabase {
+    strings: IDMap<SimpleEntity>,
+}
+
+impl Database for SimpleDatabase {
+    fn build(conn: Connection) -> Self
+    where
+        Self: Sized,
+    {
+        Self {
+            strings: IDMap::build(conn),
+        }
+    }
+
+    fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor)
+    where
+        Self: Sized,
+    {
+        <IDMap<SimpleEntity> as DatabaseItem>::accept_item_visitor(visitor);
+    }
+}
+
+#[test]
+fn part_visitor() {
+    struct V<E: Entity> {
+        v: Vec<std::any::TypeId>,
+        _ghost: std::marker::PhantomData<E>,
+    }
+    impl<E: Entity> EntityPartVisitor for V<E> {
+        type Entity = E;
+        fn visit<EP: EntityPart>(&mut self) {
+            self.v.push(std::any::TypeId::of::<EP>());
+        }
+    }
+
+    let mut vis = V {
+        v: vec![],
+        _ghost: Default::default(),
+    };
+    SimpleEntity::accept_part_visitor(&mut vis);
+    assert_eq!(
+        vis.v.as_slice(),
+        &[std::any::TypeId::of::<SimpleEntityName>()]
+    );
+}

+ 116 - 0
microrm/tests/mutual.rs

@@ -0,0 +1,116 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+struct CR;
+impl Relation for CR {
+    type Domain = Customer;
+    type Range = Receipt;
+
+    const NAME: &'static str = "CR";
+}
+
+#[derive(Entity)]
+struct Customer {
+    name: String,
+    receipts: microrm::RelationDomain<CR>,
+}
+
+#[derive(Entity)]
+struct Receipt {
+    value: usize,
+    customers: microrm::RelationRange<CR>,
+}
+
+#[derive(Database)]
+struct ReceiptDB {
+    customers: microrm::IDMap<Customer>,
+    receipts: microrm::IDMap<Receipt>,
+}
+
+#[test]
+fn check_schema_creation() {
+    let _db = common::open_test_db::<ReceiptDB>("mutual_check_schema_creation");
+}
+
+#[test]
+fn simple_operations() {
+    let db = common::open_test_db::<ReceiptDB>("mutual_simple_operations");
+
+    let ca = db
+        .customers
+        .insert(Customer {
+            name: "customer A".to_string(),
+            receipts: Default::default(),
+        })
+        .expect("couldn't insert customer record");
+
+    let ra = db
+        .receipts
+        .insert(Receipt {
+            value: 32usize,
+            customers: Default::default(),
+        })
+        .expect("couldn't insert receipt record");
+
+    let rb = db
+        .receipts
+        .insert(Receipt {
+            value: 64usize,
+            customers: Default::default(),
+        })
+        .expect("couldn't insert receipt record");
+
+    let e_ca = db
+        .customers
+        .by_id(ca)
+        .expect("couldn't retrieve customer record")
+        .expect("no customer record");
+
+    log::trace!(
+        "connecting customer a (ID {:?}) to receipt a (ID {:?})",
+        ca, ra
+    );
+    e_ca.receipts
+        .connect_to(ra)
+        .expect("couldn't relationiate customer with receipt a");
+    log::trace!(
+        "connecting customer a (ID {:?}) to receipt b (ID {:?})",
+        ca, rb
+    );
+    e_ca.receipts
+        .connect_to(rb)
+        .expect("couldn't relationiate customer with receipt b");
+
+    log::trace!("connected!");
+
+    // technically this can fail if sqlite gives ra and rb back in the opposite order, which is
+    // valid behaviour
+    assert_eq!(
+        e_ca.receipts
+            .get()
+            .expect("couldn't get receipts related with customer")
+            .into_iter()
+            .map(|x| x.id())
+            .collect::<Vec<_>>(),
+        vec![ra, rb]
+    );
+
+    // check that the reverse direction was also added
+    let e_ra = db
+        .receipts
+        .by_id(ra)
+        .expect("couldn't retreieve receipt record")
+        .expect("no receipt record");
+
+    assert_eq!(
+        e_ra.customers
+            .get()
+            .expect("couldn't get related customers")
+            .into_iter()
+            .map(|x| x.id())
+            .collect::<Vec<_>>(),
+        vec![ca]
+    );
+}

+ 154 - 0
microrm/tests/query_equiv.rs

@@ -0,0 +1,154 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+#[derive(Entity)]
+struct SingleItem {
+    #[key]
+    s: String,
+    v: Vec<u8>,
+}
+
+#[derive(Entity)]
+struct DoubleItem {
+    #[key]
+    s: String,
+    #[key]
+    t: String,
+    v: usize,
+}
+
+#[derive(Database)]
+struct ItemDB {
+    single_items: microrm::IDMap<SingleItem>,
+    double_items: microrm::IDMap<DoubleItem>,
+}
+
+#[test]
+fn single_item() {
+    let db = common::open_test_db::<ItemDB>("query_equiv_single_item");
+
+    db.single_items
+        .insert(SingleItem {
+            s: "string 1".into(),
+            v: vec![0u8, 1u8, 2u8],
+        })
+        .expect("couldn't insert test item");
+
+    db.single_items
+        .insert(SingleItem {
+            s: "string 2".into(),
+            v: vec![0u8, 1u8, 2u8],
+        })
+        .expect("couldn't insert test item");
+
+    assert!(db
+        .single_items
+        .keyed("string 2")
+        .get()
+        .expect("couldn't query db")
+        .is_some());
+    assert!(db
+        .single_items
+        .keyed("string 3")
+        .get()
+        .expect("couldn't query db")
+        .is_none());
+
+    assert!(db
+        .single_items
+        .keyed(String::from("string 2"))
+        .get()
+        .expect("couldn't query db")
+        .is_some());
+    assert!(db
+        .single_items
+        .keyed(String::from("string 3"))
+        .get()
+        .expect("couldn't query db")
+        .is_none());
+}
+
+#[test]
+fn double_item() {
+    let db = common::open_test_db::<ItemDB>("query_equiv_double_item");
+
+    db.double_items
+        .insert(DoubleItem {
+            s: "string 1".into(),
+            t: "second string 1".into(),
+            v: 42,
+        })
+        .expect("couldn't insert test item");
+
+    db.double_items
+        .insert(DoubleItem {
+            s: "string 2".into(),
+            t: "second string 2".into(),
+            v: 6,
+        })
+        .expect("couldn't insert test item");
+
+    assert!(db
+        .double_items
+        .keyed(("string 2", "second string 2"))
+        .get()
+        .expect("couldn't query db")
+        .is_some());
+    assert!(db
+        .double_items
+        .keyed(("string 3", "second string 3"))
+        .get()
+        .expect("couldn't query db")
+        .is_none());
+
+    assert!(db
+        .double_items
+        .keyed((String::from("string 2"), String::from("second string 2")))
+        .get()
+        .expect("couldn't query db")
+        .is_some());
+    assert!(db
+        .double_items
+        .keyed((String::from("string 3"), String::from("second string 3")))
+        .get()
+        .expect("couldn't query db")
+        .is_none());
+}
+
+#[test]
+fn with_test() {
+    let db = common::open_test_db::<ItemDB>("query_equiv_with");
+
+    db.single_items
+        .insert(SingleItem {
+            s: "string 1".into(),
+            v: vec![0u8, 1u8, 2u8],
+        })
+        .expect("couldn't insert test item");
+
+    db.single_items
+        .insert(SingleItem {
+            s: "string 2".into(),
+            v: vec![0u8, 1u8, 2u8],
+        })
+        .expect("couldn't insert test item");
+
+    assert_eq!(
+        db.single_items
+            .with(SingleItem::V, [0u8].as_slice())
+            .get()
+            .expect("couldn't query db")
+            .len(),
+        0
+    );
+    assert_eq!(
+        db.single_items
+            .with(SingleItem::V, [0u8, 1, 2].as_slice())
+            .get()
+            .expect("couldn't query db")
+            .len(),
+        2
+    );
+}

+ 24 - 0
microrm/tests/reserved.rs

@@ -0,0 +1,24 @@
+#![allow(unused)]
+
+use microrm::prelude::*;
+use test_log::test;
+
+#[derive(Entity)]
+struct Select {
+    delete: String,
+}
+
+#[derive(Entity)]
+struct Group {
+    by: microrm::RelationMap<Select>,
+}
+
+#[derive(Database)]
+struct ReservedWordDB {
+    group: microrm::IDMap<Group>,
+}
+
+#[test]
+fn open_test() {
+    ReservedWordDB::open_path(":memory:");
+}