Prechádzať zdrojové kódy

WIP converting to ConnectionPool and ConnectionLease.

Kestrel 4 týždňov pred
rodič
commit
a5d821fb78

+ 1 - 1
microrm-macros/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "microrm-macros"
-version = "0.4.1"
+version = "0.5.0-dev"
 edition = "2021"
 license = "BSD-4-Clause"
 authors = ["Kestrel <kestrel@flying-kestrel.ca>"]

+ 2 - 2
microrm-macros/src/database.rs

@@ -53,13 +53,13 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
         let item_name = &field.0;
         let item_type = type_to_expression_context_type(&field.1);
         quote! {
-            #item_name : #item_type :: build(conn.clone())
+            #item_name : #item_type :: build()
         }
     });
 
     quote! {
         impl ::microrm::schema::Database for #db_ident {
-            fn build(conn: ::microrm::db::Connection) -> Self where Self: Sized {
+            fn build() -> Self where Self: Sized {
                 Self { #(#build_method),* }
             }
 

+ 2 - 2
microrm/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "microrm"
-version = "0.4.4"
+version = "0.5.0-dev"
 edition = "2021"
 license = "BSD-4-Clause"
 authors = ["Kestrel <kestrel@flying-kestrel.ca>"]
@@ -25,7 +25,7 @@ time = "0.3"
 itertools = "0.12"
 thread_local = "1.1"
 
-microrm-macros = { path = "../microrm-macros", version = "0.4.1" }
+microrm-macros = { path = "../microrm-macros", version = "0.5.0-dev" }
 log = "0.4.17"
 
 clap = { version = "4", optional = true }

+ 205 - 56
microrm/src/db.rs

@@ -2,10 +2,10 @@ use crate::{DBResult, Error};
 use libsqlite3_sys as sq;
 use std::{
     cell::{Cell, RefCell},
-    collections::HashMap,
+    collections::{HashMap, VecDeque},
     ffi::{CStr, CString},
     pin::Pin,
-    sync::Arc,
+    sync::{Arc, Condvar, Mutex, Weak},
 };
 
 fn check_rcode<'a>(sql: impl FnOnce() -> Option<&'a str>, rcode: i32) -> Result<(), Error> {
@@ -22,7 +22,8 @@ fn check_rcode<'a>(sql: impl FnOnce() -> Option<&'a str>, rcode: i32) -> Result<
     }
 }
 
-struct ConnectionData {
+/// Internal struct that stores several pieces of connection-relevant data.
+pub struct ConnectionData {
     sqlite: *mut sq::sqlite3,
     stmts: RefCell<HashMap<u64, Statement>>,
 }
@@ -56,21 +57,17 @@ impl PreparedKey for std::any::TypeId {
     }
 }
 
-/// Represents a single sqlite connection, in SQLITE_MULTITHREAD mode.
-#[derive(Clone)]
-pub struct Connection(Arc<ConnectionData>);
-
-impl Connection {
+impl ConnectionData {
     /// Establish a new connection to a sqlite database object. Note that this type carries no
     /// schema information, unlike types implementing [`Database`](../schema/traits.Database.html).
-    pub fn new(url: &str) -> Result<Self, Error> {
+    fn new(uri: &str) -> Result<Self, Error> {
         let db_ptr = unsafe {
-            let url = CString::new(url)?;
+            let uri = CString::new(uri)?;
             let mut db_ptr = std::ptr::null_mut();
             check_rcode(
                 || None,
                 sq::sqlite3_open_v2(
-                    url.as_ptr(),
+                    uri.as_ptr(),
                     &mut db_ptr,
                     sq::SQLITE_OPEN_READWRITE | sq::SQLITE_OPEN_NOMUTEX | sq::SQLITE_OPEN_CREATE,
                     std::ptr::null_mut(),
@@ -89,23 +86,24 @@ impl Connection {
             sq::sqlite3_busy_timeout(db_ptr, 1000);
         }
 
-        Ok(Self(Arc::new(ConnectionData {
+        let cdata = Self {
             sqlite: db_ptr,
             stmts: Default::default(),
-        })))
+        };
+        cdata.execute_raw_sql("PRAGMA foreign_keys = ON")?;
+
+        Ok(cdata)
     }
 
     /// Execute a raw SQL statement on the database this connection represents. Use with care.
-    pub fn execute_raw_sql(&self, sql: impl AsRef<str>) -> DBResult<()> {
-        let data = self.0.as_ref();
-
+    pub(crate) fn execute_raw_sql(&self, sql: impl AsRef<str>) -> DBResult<()> {
         log::trace!("executing raw sql: {sql}", sql = sql.as_ref());
 
         unsafe {
             let c_sql = CString::new(sql.as_ref())?;
             let mut err = std::ptr::null_mut();
             let rcode = sq::sqlite3_exec(
-                data.sqlite,
+                self.sqlite,
                 c_sql.as_ptr(),
                 None,
                 std::ptr::null_mut(),
@@ -134,6 +132,12 @@ impl Connection {
 
         Ok(())
     }
+}
+
+impl<'l> ConnectionLease<'l> {
+    pub(crate) fn execute_raw_sql(&self, sql: impl AsRef<str>) -> DBResult<()> {
+        self.conn.execute_raw_sql(sql)
+    }
 
     pub(crate) fn with_prepared<R>(
         &self,
@@ -141,11 +145,10 @@ impl Connection {
         build_query: impl Fn() -> String,
         run_query: impl Fn(StatementContext) -> DBResult<R>,
     ) -> DBResult<R> {
-        let data = self.0.as_ref();
-        let conn = data.sqlite;
+        let conn = self.conn.sqlite;
 
         use std::collections::hash_map::Entry;
-        let mut stmts = data.stmts.borrow_mut();
+        let mut stmts = self.conn.stmts.borrow_mut();
         match stmts.entry(hash_key.into_u64()) {
             Entry::Vacant(e) => {
                 let sql = build_query();
@@ -182,54 +185,190 @@ impl Connection {
     }
 }
 
-struct SendWrapper<T: Clone> {
-    value: T,
+/// A temporary lease to access a database connection.
+pub struct ConnectionLease<'l> {
+    pool: Weak<ConnectionPoolData>,
+    conn: &'l ConnectionData,
+    index: usize,
 }
 
-impl<T: Clone> SendWrapper<T> {
-    fn new(value: T) -> Self {
-        Self { value }
+impl<'l> ConnectionLease<'l> {
+    /// Get the pool this lease is from.
+    pub fn pool(&self) -> ConnectionPool {
+        ConnectionPool {
+            data: self.pool.upgrade().expect("lease with no associated pool"),
+        }
     }
+}
 
-    fn get(&self) -> T {
-        self.value.clone()
+impl<'l> AsRef<ConnectionLease<'l>> for ConnectionLease<'l> {
+    fn as_ref(&self) -> &ConnectionLease<'l> {
+        self
     }
 }
 
-unsafe impl<T: Clone> Send for SendWrapper<T> {}
+/// Trait for objects that can provide a [`ConnectionLease`]
+pub trait LeaseContainer {
+    fn lease(&mut self) -> &mut ConnectionLease<'_>;
+}
 
-/// Multithreading-safe database connection pool.
-pub struct ConnectionPool {
+/// Provides access to a ConnectionLease.
+pub trait ConnectionLeaser {
+    /// Accessor function
+    fn lease<'s>(&'s self) -> impl AsRef<ConnectionLease<'s>>;
+}
+
+impl<'l> ConnectionLeaser for ConnectionLease<'l> {
+    fn lease<'s>(&'s self) -> impl AsRef<ConnectionLease<'s>> {
+        struct Access<'a, 'b: 'a>(&'a ConnectionLease<'b>);
+        impl<'a, 'b: 'a> AsRef<ConnectionLease<'a>> for Access<'a, 'b> {
+            fn as_ref(&self) -> &'a ConnectionLease<'b> {
+                self.0
+            }
+        }
+
+        Access(self)
+    }
+}
+
+impl ConnectionLeaser for ConnectionData {
+    fn lease<'s>(&'s self) -> impl AsRef<ConnectionLease<'s>> {
+        struct Access<'d>(ConnectionLease<'d>);
+        impl<'d> AsRef<ConnectionLease<'d>> for Access<'d> {
+            fn as_ref(&self) -> &ConnectionLease<'d> {
+                &self.0
+            }
+        }
+
+        Access(ConnectionLease {
+            pool: Weak::new(),
+            conn: self,
+            index: 0,
+        })
+    }
+}
+
+impl<'l> Drop for ConnectionLease<'l> {
+    fn drop(&mut self) {
+        if let Some(pooldata) = self.pool.upgrade() {
+            pooldata.release(self.index);
+        }
+    }
+}
+
+struct ConnectionPoolData {
     uri: String,
-    connections: std::sync::RwLock<HashMap<std::thread::ThreadId, SendWrapper<Connection>>>,
+    available_condition: Condvar,
+    available: Mutex<VecDeque<usize>>,
+    connections: Vec<ConnectionData>,
 }
 
-impl ConnectionPool {
-    /// Construct a new pool from a URI
-    pub fn new(uri: &str) -> Self {
+impl ConnectionPoolData {
+    fn spawn(&mut self, count: usize) -> Result<(), Error> {
+        let mut alock = self.available.lock()?;
+
+        for _ in 0..count {
+            let nconn = ConnectionData::new(self.uri.as_str())?;
+            alock.push_back(self.connections.len());
+            self.connections.push(nconn);
+            self.available_condition.notify_one();
+        }
+
+        Ok(())
+    }
+    fn acquire(self: &Arc<Self>) -> Result<ConnectionLease, Error> {
+        let mut alock = self.available.lock()?;
+
+        while alock.is_empty() {
+            alock = self.available_condition.wait(alock)?;
+        }
+
+        let index = alock.pop_back().unwrap();
+
+        Ok(ConnectionLease {
+            pool: Arc::downgrade(self),
+            conn: &self.connections[index],
+            index,
+        })
+    }
+    fn release(self: &Arc<Self>, conn: usize) {
+        let Ok(mut alock) = self.available.lock() else {
+            log::warn!("Dropping connection due to poisoned lock");
+            return;
+        };
+        alock.push_back(conn);
+        self.available_condition.notify_one();
+    }
+}
+
+/// Configuration information for a [`ConnectionPool`].
+pub struct ConnectionPoolConfig<'l> {
+    uri: &'l str,
+    pool_size: usize,
+}
+
+impl<'l> ConnectionPoolConfig<'l> {
+    const DEFAULT_POOL_SIZE: usize = 2;
+
+    /// Construct a new ConnectionPoolConfig with the given URI to pass to sqlite.
+    pub fn new(uri: &'l str) -> Self {
         Self {
-            uri: uri.into(),
-            connections: Default::default(),
+            uri,
+            pool_size: Self::DEFAULT_POOL_SIZE,
         }
     }
 
-    /// Retrieve the [`Connection`] for the current thread.
-    pub fn get(&self) -> DBResult<Connection> {
-        let thread_id = std::thread::current().id();
-        // short path: thread already has a connection
-        {
-            let cmap = self.connections.read().expect("poisoned lock");
-            if let Some(conn) = cmap.get(&thread_id) {
-                return Ok(conn.get());
-            }
+    /// Set the connection pool size.
+    pub fn with_pool_size(mut self, pool_size: usize) -> Self {
+        self.pool_size = pool_size;
+        self
+    }
+}
+
+impl<'l> From<&'l str> for ConnectionPoolConfig<'l> {
+    fn from(value: &'l str) -> Self {
+        Self {
+            uri: value,
+            pool_size: Self::DEFAULT_POOL_SIZE,
         }
-        // long path: need to construct a new connection
-        let nconn = Connection::new(self.uri.as_str())?;
+    }
+}
 
-        let mut cmap = self.connections.write().expect("poisoned lock");
-        cmap.insert(thread_id, SendWrapper::new(nconn.clone()));
+impl ConnectionLeaser for ConnectionPool {
+    fn lease<'s>(&'s self) -> impl AsRef<ConnectionLease<'s>> {
+        self.acquire().expect("implicit lease acquisition failure")
+    }
+}
+
+#[derive(Clone)]
+/// Multithreading-safe database connection pool.
+pub struct ConnectionPool {
+    data: Arc<ConnectionPoolData>,
+}
+
+impl ConnectionPool {
+    /// Construct a new pool from a URI
+    pub fn new<'l, CPC: Into<ConnectionPoolConfig<'l>>>(cpc: CPC) -> Result<Self, Error> {
+        let cpc = cpc.into();
+        let mut pooldata = ConnectionPoolData {
+            uri: cpc.uri.into(),
+            available_condition: Condvar::default(),
+            available: Default::default(),
+            connections: vec![],
+        };
 
-        Ok(nconn)
+        pooldata.spawn(cpc.pool_size)?;
+
+        Ok(Self {
+            data: Arc::new(pooldata),
+        })
+    }
+
+    /// Acquire a [`ConnectionLease`] to interact with the database.
+    ///
+    /// Note that this function may block if the pool is empty.
+    pub fn acquire(&self) -> Result<ConnectionLease<'_>, Error> {
+        self.data.acquire()
     }
 }
 
@@ -237,12 +376,12 @@ unsafe impl Send for ConnectionPool {}
 unsafe impl Sync for ConnectionPool {}
 
 pub(crate) struct Transaction<'l> {
-    db: &'l Connection,
+    db: &'l ConnectionLease<'l>,
     committed: bool,
 }
 
 impl<'l> Transaction<'l> {
-    pub fn new(db: &'l Connection) -> DBResult<Self> {
+    pub fn new(db: &'l ConnectionLease) -> DBResult<Self> {
         db.execute_raw_sql("BEGIN TRANSACTION")?;
         Ok(Self {
             db,
@@ -265,6 +404,12 @@ impl<'l> Drop for Transaction<'l> {
     }
 }
 
+impl<'l> ConnectionLeaser for Transaction<'l> {
+    fn lease<'s>(&'s self) -> impl AsRef<ConnectionLease<'s>> {
+        self.db
+    }
+}
+
 struct Statement {
     #[allow(unused)]
     sqlite: *mut sq::sqlite3,
@@ -296,18 +441,22 @@ impl Drop for Statement {
 
 #[cfg(test)]
 mod test {
-    use super::Connection;
+    use super::{ConnectionLeaser, ConnectionPool};
 
     #[test]
     fn simple_sql() {
-        let c = Connection::new(":memory:").expect("couldn't open test db");
-        c.execute_raw_sql("CREATE TABLE test_table (id integer primary key, value string)")
+        let c = ConnectionPool::new(":memory:").expect("couldn't open test db");
+        let l = c.lease();
+        l.as_ref()
+            .execute_raw_sql("CREATE TABLE test_table (id integer primary key, value string)")
             .expect("couldn't execute sql");
     }
 
     #[test]
     fn prepare_stmt() {
-        let c = Connection::new(":memory:").expect("couldn't open test db");
+        let c = ConnectionPool::new(":memory:").expect("couldn't open test db");
+        let c = c.lease();
+        let c = c.as_ref();
         c.execute_raw_sql("CREATE TABLE test_table (id integer primary key, value string)")
             .expect("couldn't execute sql");
 

+ 185 - 0
microrm/src/glue.rs

@@ -0,0 +1,185 @@
+// query implementations on schema types
+
+use crate::{
+    db::{ConnectionLease, ConnectionLeaser, StatementContext, Transaction},
+    query::{self, Insertable, Query, QueryPart, Queryable, RelationInterface},
+    schema::{
+        entity::Entity,
+        relation::{LocalSide, Relation, RelationData, RelationDomain, RelationMap, RelationRange},
+        IDMap, Stored,
+    },
+    DBResult, Error,
+};
+
+// ----------------------------------------------------------------------
+// Stored
+// ----------------------------------------------------------------------
+
+impl<T: Entity> Stored<T> {
+    /// Synchronize the wrapped value with the corresponding database row.
+    pub fn sync(&mut self, lease: &ConnectionLease) -> DBResult<()> {
+        let txn = Transaction::new(lease)?;
+        query::base_queries::update_entity(lease, self)?;
+        txn.commit()
+    }
+}
+
+// ----------------------------------------------------------------------
+// IDMap
+// ----------------------------------------------------------------------
+
+impl<T: Entity> IDMap<T> {
+    /// Look up an Entity in this map by ID.
+    pub fn by_id(&self, lease: &ConnectionLease, id: T::ID) -> DBResult<Option<Stored<T>>> {
+        self.with(T::IDPart::default(), &id).first().get(lease)
+    }
+}
+
+impl<'a, T: Entity> Queryable for &'a IDMap<T> {
+    type EntityOutput = T;
+    type OutputContainer = Vec<Stored<T>>;
+    type StaticVersion = &'static IDMap<T>;
+
+    const IS_UNIQUE: bool = false;
+
+    fn build(&self) -> Query {
+        Query::new()
+            .attach(QueryPart::Root, "SELECT DISTINCT")
+            .attach(QueryPart::Columns, "*")
+            .attach(QueryPart::From, format!("`{}`", T::entity_name()))
+    }
+    fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {}
+}
+
+impl<T: Entity> Insertable<T> for IDMap<T> {
+    fn insert(&self, lease: &ConnectionLease, value: T) -> DBResult<T::ID> {
+        let txn = Transaction::new(lease)?;
+        let out = query::base_queries::insert(txn.lease().as_ref(), &value)?;
+        txn.commit()?;
+        Ok(out)
+    }
+
+    fn insert_and_return(&self, lease: &ConnectionLease, value: T) -> DBResult<Stored<T>> {
+        let txn = Transaction::new(lease)?;
+        let out = query::base_queries::insert_and_return(txn.lease().as_ref(), value)?;
+        txn.commit()?;
+        Ok(out)
+    }
+}
+
+impl<'l, T: Entity> Insertable<T> for &'l IDMap<T> {
+    fn insert(&self, lease: &ConnectionLease, value: T) -> DBResult<T::ID> {
+        <IDMap<T> as Insertable<T>>::insert(self, lease, value)
+    }
+
+    fn insert_and_return(&self, lease: &ConnectionLease, value: T) -> DBResult<Stored<T>> {
+        <IDMap<T> as Insertable<T>>::insert_and_return(self, lease, value)
+    }
+}
+
+// ----------------------------------------------------------------------
+// RelationMap
+// ----------------------------------------------------------------------
+
+impl<T: Entity> RelationInterface for RelationMap<T> {
+    type RemoteEntity = T;
+    type StaticVersion = Self;
+    const SIDE: LocalSide = LocalSide::Domain;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError(
+                "no distinguishing name for empty RelationMap",
+            ))
+            .map(|d| d.part_name)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError("Reading from unassigned RelationMap"))
+    }
+}
+
+impl<'l, T: Entity> RelationInterface for &'l RelationMap<T> {
+    type RemoteEntity = T;
+    type StaticVersion = RelationMap<T>;
+    const SIDE: LocalSide = LocalSide::Domain;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        <RelationMap<T> as RelationInterface>::get_distinguishing_name(self)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        <RelationMap<T> as RelationInterface>::get_data(self)
+    }
+}
+
+// ----------------------------------------------------------------------
+// RelationDomain
+// ----------------------------------------------------------------------
+
+impl<R: Relation> RelationInterface for RelationDomain<R> {
+    type RemoteEntity = R::Range;
+    type StaticVersion = Self;
+    const SIDE: LocalSide = LocalSide::Domain;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        Ok(R::NAME)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError("Reading from unassigned RelationDomain"))
+    }
+}
+
+impl<'l, R: Relation> RelationInterface for &'l RelationDomain<R> {
+    type RemoteEntity = R::Range;
+    type StaticVersion = RelationDomain<R>;
+    const SIDE: LocalSide = LocalSide::Domain;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        <RelationDomain<R> as RelationInterface>::get_distinguishing_name(self)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        <RelationDomain<R> as RelationInterface>::get_data(self)
+    }
+}
+
+// ----------------------------------------------------------------------
+// RelationRange
+// ----------------------------------------------------------------------
+
+impl<R: Relation> RelationInterface for RelationRange<R> {
+    type RemoteEntity = R::Domain;
+    type StaticVersion = Self;
+    const SIDE: LocalSide = LocalSide::Range;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        Ok(R::NAME)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError("Reading from unassigned RelationRange"))
+    }
+}
+
+impl<'l, R: Relation> RelationInterface for &'l RelationRange<R> {
+    type RemoteEntity = R::Domain;
+    type StaticVersion = RelationRange<R>;
+    const SIDE: LocalSide = LocalSide::Range;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        <RelationRange<R> as RelationInterface>::get_distinguishing_name(self)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        <RelationRange<R> as RelationInterface>::get_data(self)
+    }
+}

+ 4 - 0
microrm/src/lib.rs

@@ -225,6 +225,10 @@ pub mod db;
 mod query;
 pub mod schema;
 
+mod glue;
+
+// re-exports
+pub use db::{ConnectionLease, ConnectionPool};
 pub use schema::index::Index;
 pub use schema::relation::{Relation, RelationDomain, RelationMap, RelationRange};
 pub use schema::{IDMap, Serialized, Stored};

+ 52 - 66
microrm/src/query.rs

@@ -1,13 +1,13 @@
 use itertools::Itertools;
 
 use crate::{
-    db::{Connection, StatementContext, Transaction},
+    db::{ConnectionLease, ConnectionLeaser, StatementContext, Transaction},
     schema::{
         datum::{Datum, QueryEquivalent, QueryEquivalentList},
         entity::{Entity, EntityID, EntityPart, EntityPartList},
         index::Index,
         relation::{LocalSide, RelationData},
-        IDMap, Stored,
+        Stored,
     },
     DBResult, Error,
 };
@@ -206,36 +206,44 @@ pub trait RelationInterface {
 
     /// Query this entity type without the relation filter.
     fn query_all(&self) -> impl Queryable<EntityOutput = Self::RemoteEntity> {
-        components::TableComponent::<Self::RemoteEntity>::new(self.get_data().unwrap().conn.clone())
+        components::TableComponent::<Self::RemoteEntity>::new()
     }
 
     /// Attempt to connect the contextual instance to a remote instance.
-    fn connect_to(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
+    fn connect_to(
+        &self,
+        lease: &ConnectionLease,
+        remote_id: <Self::RemoteEntity as Entity>::ID,
+    ) -> DBResult<()>
     where
         Self: Sized,
     {
         let rdata = self.get_data()?;
         let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&rdata.conn)?;
+        let txn = Transaction::new(lease)?;
 
-        base_queries::do_connect::<Self::RemoteEntity>(rdata, an, remote_id)?;
+        base_queries::do_connect::<Self::RemoteEntity>(lease, rdata, an, remote_id)?;
 
         txn.commit()
     }
 
     /// Attempt to disconnect the contextual instance from a remote instance.
-    fn disconnect_from(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
+    fn disconnect_from(
+        &self,
+        lease: &ConnectionLease,
+        remote_id: <Self::RemoteEntity as Entity>::ID,
+    ) -> DBResult<()>
     where
         Self: Sized,
     {
         let rdata = self.get_data()?;
         let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&rdata.conn)?;
+        let txn = Transaction::new(lease)?;
 
         // second, add to the relation table
-        rdata.conn.with_prepared(
+        txn.lease().as_ref().with_prepared(
             hash_of(("disconnect", an.local_name, an.remote_name, an.part_name)),
             || {
                 format!(
@@ -264,14 +272,18 @@ pub trait RelationInterface {
 /// Represents a context in which we can insert an entity type `E`.
 pub trait Insertable<E: Entity> {
     /// Insert an entity instance and return its new ID.
-    fn insert(&self, value: E) -> DBResult<E::ID>;
+    fn insert(&self, lease: &ConnectionLease, value: E) -> DBResult<E::ID>;
     /// Insert an entity instance and return a [`Stored`] instance that can be used to synchronize
     /// its values back into the database later.
-    fn insert_and_return(&self, value: E) -> DBResult<Stored<E>>;
+    fn insert_and_return(&self, lease: &ConnectionLease, value: E) -> DBResult<Stored<E>>;
 }
 
 impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
-    fn insert(&self, value: AI::RemoteEntity) -> DBResult<<AI::RemoteEntity as Entity>::ID>
+    fn insert(
+        &self,
+        lease: &ConnectionLease,
+        value: AI::RemoteEntity,
+    ) -> DBResult<<AI::RemoteEntity as Entity>::ID>
     where
         Self: Sized,
     {
@@ -282,19 +294,23 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
         let rdata = self.get_data()?;
         let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&rdata.conn)?;
+        let txn = Transaction::new(lease)?;
 
         // so first, into the remote table
-        let remote_id = base_queries::insert(&rdata.conn, &value)?;
+        let remote_id = base_queries::insert(lease, &value)?;
         // then the relation
-        base_queries::do_connect::<AI::RemoteEntity>(rdata, an, remote_id)?;
+        base_queries::do_connect::<AI::RemoteEntity>(lease, rdata, an, remote_id)?;
 
         txn.commit()?;
 
         Ok(remote_id)
     }
 
-    fn insert_and_return(&self, value: AI::RemoteEntity) -> DBResult<Stored<AI::RemoteEntity>>
+    fn insert_and_return(
+        &self,
+        lease: &ConnectionLease,
+        value: AI::RemoteEntity,
+    ) -> DBResult<Stored<AI::RemoteEntity>>
     where
         Self: Sized,
     {
@@ -305,12 +321,12 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
         let rdata = self.get_data()?;
         let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&rdata.conn)?;
+        let txn = Transaction::new(lease)?;
 
         // so first, into the remote table
-        let remote = base_queries::insert_and_return(&rdata.conn, value)?;
+        let remote = base_queries::insert_and_return(lease, value)?;
         // then the relation
-        base_queries::do_connect::<AI::RemoteEntity>(rdata, an, remote.id())?;
+        base_queries::do_connect::<AI::RemoteEntity>(lease, rdata, an, remote.id())?;
 
         txn.commit()?;
 
@@ -336,8 +352,6 @@ pub trait Queryable: Clone {
     /// Bind into any required placeholders to 'fill' an instance created by [`build`].
     #[doc(hidden)]
     fn bind(&self, stmt: &mut StatementContext, index: &mut i32);
-    #[doc(hidden)]
-    fn conn(&self) -> &Connection;
 
     // ----------------------------------------------------------------------
     // Verbs
@@ -345,13 +359,13 @@ pub trait Queryable: Clone {
     /// Count all entities in the current context.
     ///
     /// Returns the number of entities.
-    fn count(self) -> DBResult<usize>
+    fn count(self, lease: &ConnectionLease) -> DBResult<usize>
     where
         Self: Sized,
     {
-        let txn = Transaction::new(self.conn())?;
+        let txn = Transaction::new(lease)?;
         struct CountTag;
-        let out = self.conn().with_prepared(
+        let out = txn.lease().as_ref().with_prepared(
             std::any::TypeId::of::<(Self::StaticVersion, CountTag)>(),
             || {
                 self.build()
@@ -379,13 +393,13 @@ pub trait Queryable: Clone {
         Ok(out)
     }
     /// Get all entities in the current context.
-    fn get(self) -> DBResult<Self::OutputContainer>
+    fn get(self, lease: &ConnectionLease) -> DBResult<Self::OutputContainer>
     where
         Self: Sized,
     {
-        let txn = Transaction::new(self.conn())?;
+        let txn = Transaction::new(lease)?;
         struct GetTag;
-        let out = self.conn().with_prepared(
+        let out = txn.lease().as_ref().with_prepared(
             std::any::TypeId::of::<(Self::StaticVersion, GetTag)>(),
             || self.build().assemble(),
             |mut ctx| {
@@ -393,7 +407,7 @@ pub trait Queryable: Clone {
                 let mut index = 1;
                 self.bind(&mut ctx, &mut index);
 
-                <Self::OutputContainer>::assemble_from(self.conn(), ctx)
+                <Self::OutputContainer>::assemble_from(ctx)
             },
         )?;
         txn.commit()?;
@@ -402,13 +416,14 @@ pub trait Queryable: Clone {
     /// Get IDs of all entities in the current context.
     fn get_ids(
         self,
+        lease: &ConnectionLease,
     ) -> DBResult<<Self::OutputContainer as OutputContainer<Self::EntityOutput>>::IDContainer>
     where
         Self: Sized,
     {
-        let txn = Transaction::new(self.conn())?;
+        let txn = Transaction::new(lease)?;
         struct GetIDTag;
-        let out = self.conn().with_prepared(
+        let out = txn.lease().as_ref().with_prepared(
             std::any::TypeId::of::<(Self::StaticVersion, GetIDTag)>(),
             || {
                 self.build()
@@ -432,13 +447,13 @@ pub trait Queryable: Clone {
         Ok(out)
     }
     /// Delete all entities in the current context.
-    fn delete(self) -> DBResult<()>
+    fn delete(self, lease: &ConnectionLease) -> DBResult<()>
     where
         Self: Sized,
     {
-        let txn = Transaction::new(self.conn())?;
+        let txn = Transaction::new(lease)?;
         struct DeleteTag;
-        self.conn().with_prepared(
+        txn.lease().as_ref().with_prepared(
             std::any::TypeId::of::<(Self::StaticVersion, DeleteTag)>(),
             || {
                 format!(
@@ -465,13 +480,13 @@ pub trait Queryable: Clone {
     }
 
     /// Delete all entities in the current context and return them
-    fn remove(self) -> DBResult<Self::OutputContainer>
+    fn remove(self, lease: &ConnectionLease) -> DBResult<Self::OutputContainer>
     where
         Self: Sized,
     {
-        let txn = Transaction::new(self.conn())?;
+        let txn = Transaction::new(lease)?;
         struct DeleteTag;
-        let out = self.conn().with_prepared(
+        let out = txn.lease().as_ref().with_prepared(
             std::any::TypeId::of::<(Self::StaticVersion, DeleteTag)>(),
             || {
                 format!(
@@ -491,7 +506,7 @@ pub trait Queryable: Clone {
                 let mut index = 1;
                 self.bind(&mut ctx, &mut index);
 
-                <Self::OutputContainer>::assemble_from(self.conn(), ctx)
+                <Self::OutputContainer>::assemble_from(ctx)
             },
         )?;
         txn.commit()?;
@@ -609,27 +624,6 @@ pub trait Queryable: Clone {
     }
 }
 
-// Generic implementations for all IDMaps
-impl<'a, T: Entity> Queryable for &'a IDMap<T> {
-    type EntityOutput = T;
-    type OutputContainer = Vec<Stored<T>>;
-    type StaticVersion = &'static IDMap<T>;
-
-    const IS_UNIQUE: bool = false;
-
-    fn build(&self) -> Query {
-        Query::new()
-            .attach(QueryPart::Root, "SELECT DISTINCT")
-            .attach(QueryPart::Columns, "*")
-            .attach(QueryPart::From, format!("`{}`", T::entity_name()))
-    }
-    fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {}
-
-    fn conn(&self) -> &Connection {
-        &self.conn
-    }
-}
-
 // Generic implementation for all relation specification types
 impl<'a, AI: RelationInterface> Queryable for &'a AI {
     type EntityOutput = AI::RemoteEntity;
@@ -666,10 +660,6 @@ impl<'a, AI: RelationInterface> Queryable for &'a AI {
             .expect("couldn't bind relation id");
         *index += 1;
     }
-
-    fn conn(&self) -> &Connection {
-        &self.get_data().unwrap().conn
-    }
 }
 
 impl<E: Entity, EPL: EntityPartList<Entity = E>> Index<E, EPL> {
@@ -698,8 +688,4 @@ impl<'a, E: Entity, EPL: EntityPartList<Entity = E>> Queryable for &'a Index<E,
             .attach(QueryPart::From, format!("`{}`", E::entity_name()))
     }
     fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {}
-
-    fn conn(&self) -> &Connection {
-        &self.conn
-    }
 }

+ 15 - 12
microrm/src/query/base_queries.rs

@@ -1,5 +1,5 @@
 use crate::{
-    db::{Connection, StatementContext},
+    db::{ConnectionLease, StatementContext},
     schema::{
         datum::Datum,
         entity::{helpers::is_relation, Entity, EntityID, EntityPart, EntityPartVisitor},
@@ -11,7 +11,7 @@ use crate::{
 
 use super::RelationNames;
 
-pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID> {
+pub(crate) fn insert<E: Entity>(conn: &ConnectionLease, value: &E) -> DBResult<E::ID> {
     struct InsertQuery<E: Entity>(std::marker::PhantomData<E>);
 
     conn.with_prepared(
@@ -85,29 +85,31 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
     )
 }
 
-pub(crate) fn insert_and_return<E: Entity>(conn: &Connection, mut value: E) -> DBResult<Stored<E>> {
-    let id = insert(conn, &value)?;
+pub(crate) fn insert_and_return<E: Entity>(
+    lease: &ConnectionLease,
+    mut value: E,
+) -> DBResult<Stored<E>> {
+    let id = insert(lease, &value)?;
 
     // update relation data in all fields
-    struct DatumWalker<'l, E: Entity>(&'l Connection, i64, std::marker::PhantomData<E>);
-    impl<'l, E: Entity> EntityPartVisitor for DatumWalker<'l, E> {
+    struct DatumWalker<E: Entity>(i64, std::marker::PhantomData<E>);
+    impl<E: Entity> EntityPartVisitor for DatumWalker<E> {
         type Entity = E;
         fn visit_datum_mut<EP: EntityPart>(&mut self, datum: &mut EP::Datum) {
             datum.update_adata(RelationData {
-                conn: self.0.clone(),
                 part_name: EP::part_name(),
                 local_name: <EP::Entity as Entity>::entity_name(),
-                local_id: self.1,
+                local_id: self.0,
             });
         }
     }
 
-    value.accept_part_visitor_mut(&mut DatumWalker(conn, id.into_raw(), Default::default()));
+    value.accept_part_visitor_mut(&mut DatumWalker(id.into_raw(), Default::default()));
 
-    Ok(Stored::new(conn.clone(), id, value))
+    Ok(Stored::new(id, value))
 }
 
-pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) -> DBResult<()> {
+pub(crate) fn update_entity<E: Entity>(conn: &ConnectionLease, value: &Stored<E>) -> DBResult<()> {
     struct UpdateQuery<E: Entity>(std::marker::PhantomData<E>);
 
     conn.with_prepared(
@@ -177,11 +179,12 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
 }
 
 pub(crate) fn do_connect<Remote: Entity>(
+    lease: &ConnectionLease,
     rdata: &RelationData,
     an: RelationNames,
     remote_id: Remote::ID,
 ) -> DBResult<()> {
-    rdata.conn.with_prepared(
+    lease.with_prepared(
         super::hash_of(("connect", an.local_name, an.remote_name, an.part_name)),
         || {
             format!(

+ 3 - 35
microrm/src/query/components.rs

@@ -1,9 +1,8 @@
 //! Component types for query construction.
 
 use crate::{
-    db::{Connection, StatementContext},
-    prelude::Queryable,
-    query::{QueryPart, RelationInterface},
+    db::StatementContext,
+    query::{QueryPart, Queryable, RelationInterface},
     schema::{
         datum::{Datum, DatumDiscriminator, DatumVisitor, QueryEquivalent, QueryEquivalentList},
         entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor},
@@ -16,23 +15,20 @@ use super::{OutputContainer, Query};
 
 /// Allow manipulation of an entire table.
 pub(crate) struct TableComponent<E: Entity> {
-    conn: Connection,
     _ghost: std::marker::PhantomData<E>,
 }
 
 impl<E: Entity> Clone for TableComponent<E> {
     fn clone(&self) -> Self {
         Self {
-            conn: self.conn.clone(),
             _ghost: Default::default(),
         }
     }
 }
 
 impl<E: Entity> TableComponent<E> {
-    pub fn new(conn: Connection) -> Self {
+    pub fn new() -> Self {
         Self {
-            conn,
             _ghost: Default::default(),
         }
     }
@@ -52,9 +48,6 @@ impl<E: Entity> Queryable for TableComponent<E> {
             .attach(QueryPart::From, format!("`{}`", E::entity_name()))
     }
     fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {}
-    fn conn(&self) -> &Connection {
-        &self.conn
-    }
 }
 
 /// Filter on a Datum
@@ -101,9 +94,6 @@ impl<WEP: EntityPart, Parent: Queryable + 'static> Queryable
     fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {
         unreachable!()
     }
-    fn conn(&self) -> &Connection {
-        unreachable!()
-    }
 }
 
 impl<
@@ -133,10 +123,6 @@ impl<
         self.datum.bind_to(stmt, *index);
         *index += 1;
     }
-
-    fn conn(&self) -> &Connection {
-        self.parent.conn()
-    }
 }
 
 /// Filter on an index
@@ -209,9 +195,6 @@ impl<E: Entity, Parent: Queryable + 'static, EPL: EntityPartList<Entity = E>> Qu
     fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {
         unreachable!()
     }
-    fn conn(&self) -> &Connection {
-        unreachable!()
-    }
 }
 
 impl<E: Entity, Parent: Queryable + 'static, EPL: EntityPartList<Entity = E>> Clone
@@ -273,10 +256,6 @@ impl<
 
         self.datum.accept(&mut Visitor(stmt, index));
     }
-
-    fn conn(&self) -> &Connection {
-        self.parent.conn()
-    }
 }
 
 #[derive(Clone)]
@@ -309,10 +288,6 @@ impl<Parent: Queryable> Queryable for SingleComponent<Parent> {
     fn bind(&self, stmt: &mut StatementContext, index: &mut i32) {
         self.parent.bind(stmt, index)
     }
-
-    fn conn(&self) -> &Connection {
-        self.parent.conn()
-    }
 }
 
 /// Join with another entity via an relation
@@ -427,10 +402,6 @@ impl<
     fn bind(&self, stmt: &mut StatementContext, index: &mut i32) {
         self.parent.bind(stmt, index);
     }
-
-    fn conn(&self) -> &Connection {
-        self.parent.conn()
-    }
 }
 
 /// Get an entity via a foreign key.
@@ -483,7 +454,4 @@ impl<FE: Entity, EP: EntityPart, Parent: Queryable> Queryable for ForeignCompone
     fn bind(&self, stmt: &mut StatementContext, index: &mut i32) {
         self.parent.bind(stmt, index)
     }
-    fn conn(&self) -> &Connection {
-        self.parent.conn()
-    }
 }

+ 9 - 9
microrm/src/query/containers.rs

@@ -1,5 +1,5 @@
 use crate::{
-    db::{Connection, StatementContext, StatementRow},
+    db::{StatementContext, StatementRow},
     schema::{
         entity::{Entity, EntityID, EntityPartList},
         Stored,
@@ -16,7 +16,7 @@ pub trait IDContainer<T: Entity>: 'static + IntoIterator<Item = T::ID> {
 pub trait OutputContainer<T: Entity>: 'static + IntoIterator<Item = Stored<T>> {
     type IDContainer: IDContainer<T>;
     type ReplacedEntity<N: Entity>: OutputContainer<N>;
-    fn assemble_from(conn: &Connection, stmt: StatementContext<'_>) -> DBResult<Self>
+    fn assemble_from(stmt: StatementContext<'_>) -> DBResult<Self>
     where
         Self: Sized;
 }
@@ -25,10 +25,10 @@ fn assemble_id<T: Entity>(row: StatementRow) -> T::ID {
     <T::ID>::from_raw(row.read::<i64>(0).expect("couldn't read ID"))
 }
 
-fn assemble_single<T: Entity>(conn: &Connection, row: &mut StatementRow) -> Stored<T> {
+fn assemble_single<T: Entity>(row: &mut StatementRow) -> Stored<T> {
     let id = row.read::<i64>(0).expect("couldn't read ID");
-    let datum_list = <T::Parts>::build_datum_list(conn, row).expect("couldn't build datum list");
-    Stored::new(conn.clone(), T::ID::from_raw(id), T::build(datum_list))
+    let datum_list = <T::Parts>::build_datum_list(row).expect("couldn't build datum list");
+    Stored::new(T::ID::from_raw(id), T::build(datum_list))
 }
 
 impl<T: Entity> IDContainer<T> for Option<T::ID> {
@@ -44,11 +44,11 @@ impl<T: Entity> OutputContainer<T> for Option<Stored<T>> {
     type IDContainer = Option<T::ID>;
     type ReplacedEntity<N: Entity> = Option<Stored<N>>;
 
-    fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
+    fn assemble_from(ctx: StatementContext<'_>) -> DBResult<Self>
     where
         Self: Sized,
     {
-        Ok(ctx.run()?.map(|mut r| assemble_single(conn, &mut r)))
+        Ok(ctx.run()?.map(|mut r| assemble_single(&mut r)))
     }
 }
 
@@ -67,12 +67,12 @@ impl<T: Entity> OutputContainer<T> for Vec<Stored<T>> {
     type IDContainer = Vec<T::ID>;
     type ReplacedEntity<N: Entity> = Vec<Stored<N>>;
 
-    fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
+    fn assemble_from(ctx: StatementContext<'_>) -> DBResult<Self>
     where
         Self: Sized,
     {
         ctx.iter()
-            .map(|r| r.map(|mut s| assemble_single(conn, &mut s)))
+            .map(|r| r.map(|mut s| assemble_single(&mut s)))
             .collect::<Result<Vec<_>, Error>>()
     }
 }

+ 38 - 69
microrm/src/schema.rs

@@ -6,19 +6,14 @@
 //! - *local*: the current side of a relation
 //! - *remote*: the opposite side of a relation
 
-use query::Queryable;
-
 use crate::{
-    db::{Connection, StatementContext, StatementRow, Transaction},
-    query::{self, Insertable},
-    schema::datum::{Datum, DatumDiscriminator},
-    schema::entity::Entity,
+    db::{ConnectionLease, StatementContext, StatementRow},
+    DBResult, Error,
 };
-use crate::{DBResult, Error};
 
 use self::{
-    datum::{ConcreteDatum, DatumDiscriminatorRef},
-    entity::EntityPartList,
+    datum::{ConcreteDatum, Datum, DatumDiscriminator, DatumDiscriminatorRef},
+    entity::{Entity, EntityPartList},
 };
 
 /// Types related to datums, or struct fields.
@@ -43,18 +38,13 @@ pub(crate) mod meta;
 
 /// Wrapper struct that holds both an EntityID and an Entity itself.
 pub struct Stored<T: Entity> {
-    db: Connection,
     id: T::ID,
     wrap: T,
 }
 
 impl<T: Entity> Stored<T> {
-    pub(crate) fn new(db: Connection, id: T::ID, value: T) -> Self {
-        Self {
-            db,
-            id,
-            wrap: value,
-        }
+    pub(crate) fn new(id: T::ID, value: T) -> Self {
+        Self { id, wrap: value }
     }
 
     /// Retrieve the entity ID of the stored entity.
@@ -66,13 +56,6 @@ impl<T: Entity> Stored<T> {
     pub fn wrapped(self) -> T {
         self.wrap
     }
-
-    /// Synchronize the wrapped value with the corresponding database row.
-    pub fn sync(&mut self) -> DBResult<()> {
-        let txn = Transaction::new(&self.db)?;
-        query::base_queries::update_entity(&self.db, self)?;
-        txn.commit()
-    }
 }
 
 impl<T: Entity + std::fmt::Debug> std::fmt::Debug for Stored<T> {
@@ -112,7 +95,6 @@ impl<T: Entity> std::ops::DerefMut for Stored<T> {
 impl<T: Entity + Clone> Clone for Stored<T> {
     fn clone(&self) -> Self {
         Self {
-            db: self.db.clone(),
             id: self.id,
             wrap: self.wrap.clone(),
         }
@@ -273,14 +255,12 @@ impl<T: 'static + serde::Serialize + serde::de::DeserializeOwned + std::fmt::Deb
 
 /// Table with EntityID-based lookup.
 pub struct IDMap<T: Entity> {
-    pub(crate) conn: Connection,
     _ghost: std::marker::PhantomData<T>,
 }
 
 impl<T: Entity> Clone for IDMap<T> {
     fn clone(&self) -> Self {
         Self {
-            conn: self.conn.clone(),
             _ghost: Default::default(),
         }
     }
@@ -288,47 +268,11 @@ impl<T: Entity> Clone for IDMap<T> {
 
 impl<T: Entity> IDMap<T> {
     /// Construct a non-empty instance of an `IDMap`.
-    pub fn build(db: Connection) -> Self {
+    pub fn build() -> Self {
         Self {
-            conn: db,
             _ghost: std::marker::PhantomData,
         }
     }
-
-    pub(crate) fn conn(&self) -> &Connection {
-        &self.conn
-    }
-
-    /// Look up an Entity in this map by ID.
-    pub fn by_id(&self, id: T::ID) -> DBResult<Option<Stored<T>>> {
-        self.with(T::IDPart::default(), &id).first().get()
-    }
-}
-
-impl<T: Entity> Insertable<T> for IDMap<T> {
-    fn insert(&self, value: T) -> DBResult<T::ID> {
-        let txn = Transaction::new(self.conn())?;
-        let out = query::base_queries::insert(self.conn(), &value)?;
-        txn.commit()?;
-        Ok(out)
-    }
-
-    fn insert_and_return(&self, value: T) -> DBResult<Stored<T>> {
-        let txn = Transaction::new(self.conn())?;
-        let out = query::base_queries::insert_and_return(self.conn(), value)?;
-        txn.commit()?;
-        Ok(out)
-    }
-}
-
-impl<'l, T: Entity> Insertable<T> for &'l IDMap<T> {
-    fn insert(&self, value: T) -> DBResult<T::ID> {
-        <IDMap<T> as Insertable<T>>::insert(self, value)
-    }
-
-    fn insert_and_return(&self, value: T) -> DBResult<Stored<T>> {
-        <IDMap<T> as Insertable<T>>::insert_and_return(self, value)
-    }
 }
 
 impl<E: Entity> DatabaseItem for IDMap<E> {
@@ -357,6 +301,7 @@ pub trait DatabaseItemVisitor {
 
 /// A root structure for the database specification graph.
 pub trait Database {
+    /*
     /// Open the SQLite database at the given URI and return it as an instance of a schema.
     ///
     /// This function will attempt to create the database file if it is not present.
@@ -364,26 +309,50 @@ pub trait Database {
     where
         Self: Sized,
     {
-        let conn = Connection::new(uri.as_ref())?;
+        let pool = ConnectionPool::new(uri.as_ref())?;
+        Self::wrap_pool(pool)
+    }
+
+    fn wrap_pool(pool: ConnectionPool) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
         let schema = build::collect_from_database::<Self>();
-        match schema.check(conn.clone()) {
+        match schema.check(&pool) {
             // schema checks out
             Some(true) => {},
             // schema doesn't match
             Some(false) => Err(Error::IncompatibleSchema)?,
             // no schema found
             None => {
-                schema.create(conn.clone())?;
+                schema.create(&pool)?;
             },
         }
 
-        conn.execute_raw_sql("PRAGMA foreign_keys = ON")?;
+        Ok(Self::build(pool))
+    }*/
 
-        Ok(Self::build(conn))
+    ///
+    fn install_schema(lease: &ConnectionLease) -> DBResult<()>
+    where
+        Self: Sized,
+    {
+        let schema = build::collect_from_database::<Self>();
+        match schema.check(lease) {
+            // schema checks out
+            Some(true) => {},
+            // schema doesn't match
+            Some(false) => Err(Error::IncompatibleSchema)?,
+            // no schema found
+            None => {
+                schema.create(lease)?;
+            },
+        }
+        Ok(())
     }
 
     #[doc(hidden)]
-    fn build(conn: Connection) -> Self
+    fn build() -> Self
     where
         Self: Sized;
 

+ 19 - 14
microrm/src/schema/build.rs

@@ -1,9 +1,11 @@
 use crate::{
+    db::ConnectionLease,
     prelude::*,
+    query::{Insertable, Queryable},
     schema::{
         collect::{EntityStateContainer, PartType},
         entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor},
-        meta, Connection, DatabaseItemVisitor,
+        meta, DatabaseItemVisitor,
     },
     DBResult,
 };
@@ -101,41 +103,44 @@ impl DatabaseSchema {
     /// - yes, this is a schema match (true)
     /// - no, this is not a schema match (false)
     /// - there is no schema that we know of (None)
-    pub fn check(&self, db: Connection) -> Option<bool> {
+    pub fn check(&self, lease: &ConnectionLease) -> Option<bool> {
         // attempt to use connection as a MetadataDB database
-        let metadb = meta::MetadataDB::build(db);
+        let metadb = meta::MetadataDB::build();
 
         // check to see if the signature exists and matches
         metadb
             .metastore
             .keyed(&Self::SCHEMA_SIGNATURE_KEY.to_string())
-            .get()
+            .get(lease)
             .ok()
             .flatten()
             .map(|kv| kv.value.parse::<u64>().unwrap_or(0) == self.signature)
     }
 
-    pub fn create(&self, db: Connection) -> DBResult<()> {
-        db.execute_raw_sql("BEGIN TRANSACTION")?;
+    pub fn create(&self, lease: &ConnectionLease) -> DBResult<()> {
+        lease.execute_raw_sql("BEGIN TRANSACTION")?;
         for query in self.queries.iter() {
             log::trace!("Running creation query {query}");
-            db.execute_raw_sql(query)?;
+            lease.execute_raw_sql(query)?;
         }
 
         // attempt to use connection as a MetadataDB database
-        let metadb = meta::MetadataDB::build(db.clone());
+        let metadb = meta::MetadataDB::build();
 
         for query in collect_from_database::<meta::MetadataDB>().queries.iter() {
-            db.execute_raw_sql(query)?;
+            lease.execute_raw_sql(query)?;
         }
 
-        db.execute_raw_sql("COMMIT")?;
+        lease.execute_raw_sql("COMMIT")?;
 
         // store signature
-        metadb.metastore.insert(meta::MicrormMeta {
-            key: Self::SCHEMA_SIGNATURE_KEY.into(),
-            value: format!("{}", self.signature),
-        })?;
+        metadb.metastore.insert(
+            lease,
+            meta::MicrormMeta {
+                key: Self::SCHEMA_SIGNATURE_KEY.into(),
+                value: format!("{}", self.signature),
+            },
+        )?;
 
         Ok(())
     }

+ 2 - 6
microrm/src/schema/entity.rs

@@ -1,10 +1,6 @@
 use std::{fmt::Debug, hash::Hash};
 
-use crate::{
-    db::{Connection, StatementRow},
-    schema::datum::Datum,
-    DBResult,
-};
+use crate::{db::StatementRow, schema::datum::Datum, DBResult};
 
 use super::datum::{ConcreteDatum, ConcreteDatumList, QueryEquivalentList};
 
@@ -75,7 +71,7 @@ pub trait EntityPartList: 'static {
     const IS_EMPTY: bool = false;
 
     /// Construct an instance of `Self::DatumList` from a table row.
-    fn build_datum_list(conn: &Connection, stmt: &mut StatementRow) -> DBResult<Self::DatumList>;
+    fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList>;
 
     /// Accept a visitor to iterate across each part in the list, with no datum instances.
     fn accept_part_visitor(_: &mut impl EntityPartVisitor<Entity = Self::Entity>);

+ 10 - 15
microrm/src/schema/entity/part_list.rs

@@ -1,16 +1,11 @@
-use crate::{
-    db::{Connection, StatementRow},
-    schema::relation::RelationData,
-    DBResult,
-};
+use crate::{db::StatementRow, schema::relation::RelationData, DBResult};
 
 use super::{Datum, Entity, EntityPart, EntityPartList, EntityPartVisitor};
 
 macro_rules! build_datum {
-    ($conn:ident,$local_id:ident,$stmt:ident,$idx:ident,$d:ident,$t:ident) => {
+    ($local_id:ident,$stmt:ident,$idx:ident,$d:ident,$t:ident) => {
         let $d = <$t::Datum as Datum>::build_from(
             RelationData {
-                conn: $conn.clone(),
                 local_name: <$t::Entity as Entity>::entity_name(),
                 part_name: $t::part_name(),
                 local_id: $local_id,
@@ -72,7 +67,7 @@ impl<E: Entity> EntityPartList for EmptyList<E> {
     type ListTail = EmptyList<E>;
     const IS_EMPTY: bool = true;
 
-    fn build_datum_list(_conn: &Connection, _stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
+    fn build_datum_list(_stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
         Ok(())
     }
 
@@ -87,10 +82,10 @@ impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for P0 {
     type ListHead = P0;
     type ListTail = EmptyList<E>;
 
-    fn build_datum_list(conn: &Connection, stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
+    fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
         let local_id: i64 = stmt.read(0)?;
         let mut idx = 1; // starting index is 1 since index 0 is the ID
-        build_datum!(conn, local_id, stmt, idx, d0, P0);
+        build_datum!(local_id, stmt, idx, d0, P0);
 
         Ok(d0)
     }
@@ -113,8 +108,8 @@ impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for (P0,) {
     type ListHead = P0;
     type ListTail = EmptyList<E>;
 
-    fn build_datum_list(conn: &Connection, stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
-        <P0 as EntityPartList>::build_datum_list(conn, stmt)
+    fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
+        <P0 as EntityPartList>::build_datum_list(stmt)
     }
 
     fn accept_part_visitor(v: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
@@ -137,12 +132,12 @@ macro_rules! part_list_impl {
             type ListHead = $p0;
             type ListTail = ( $( $p ),* , );
 
-            fn build_datum_list(conn: &Connection, stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
+            fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
                 let local_id: i64 = stmt.read(0)?;
                 let mut idx = 1;
-                build_datum!(conn, local_id, stmt, idx, $d0, $p0);
+                build_datum!(local_id, stmt, idx, $d0, $p0);
                 $(
-                    build_datum!(conn, local_id, stmt, idx, $d, $p);
+                    build_datum!(local_id, stmt, idx, $d, $p);
                 )*
 
                 Ok(( $d0, $( $d ),* ))

+ 2 - 7
microrm/src/schema/index.rs

@@ -1,7 +1,4 @@
-use crate::{
-    db::Connection,
-    schema::entity::{Entity, EntityPart, EntityPartList},
-};
+use crate::schema::entity::{Entity, EntityPart, EntityPartList};
 
 /// Trait used to get entity part types by index, used for index schema generation.
 pub trait IndexedEntityPart<const N: usize> {
@@ -23,15 +20,13 @@ pub trait IndexPartList<E: Entity, II> {
 /// A search index across given fields. Note that since this is a unique index, it also enforces a
 /// uniqueness constraint across the fields.
 pub struct Index<E: Entity, EPL: EntityPartList<Entity = E>> {
-    pub(crate) conn: Connection,
     _ghost: std::marker::PhantomData<(E, EPL)>,
 }
 
 impl<E: Entity, EPL: EntityPartList<Entity = E>> Index<E, EPL> {
     /// Construct an Index instance.
-    pub fn build(conn: Connection) -> Self {
+    pub fn build() -> Self {
         Self {
-            conn,
             _ghost: std::marker::PhantomData,
         }
     }

+ 2 - 99
microrm/src/schema/relation.rs

@@ -1,11 +1,10 @@
 use crate::{
-    db::{Connection, StatementContext, StatementRow},
-    query::RelationInterface,
+    db::{StatementContext, StatementRow},
     schema::{
         datum::{ConcreteDatum, Datum, DatumDiscriminator, DatumDiscriminatorRef},
         entity::{Entity, EntityVisitor},
     },
-    DBResult, Error,
+    DBResult,
 };
 
 /// Represents an arbitrary relation between two types of entities.
@@ -43,7 +42,6 @@ pub enum LocalSide {
 /// Opaque data structure used for constructing `Relation{Map,Domain,Range}` instances.
 #[derive(Clone)]
 pub struct RelationData {
-    pub(crate) conn: Connection,
     pub(crate) local_name: &'static str,
     pub(crate) part_name: &'static str,
     pub(crate) local_id: i64,
@@ -84,41 +82,6 @@ impl<T: Entity> Default for RelationMap<T> {
     }
 }
 
-impl<T: Entity> RelationInterface for RelationMap<T> {
-    type RemoteEntity = T;
-    type StaticVersion = Self;
-    const SIDE: LocalSide = LocalSide::Domain;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError(
-                "no distinguishing name for empty RelationMap",
-            ))
-            .map(|d| d.part_name)
-    }
-
-    fn get_data(&self) -> DBResult<&RelationData> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError("Reading from unassigned RelationMap"))
-    }
-}
-
-impl<'l, T: Entity> RelationInterface for &'l RelationMap<T> {
-    type RemoteEntity = T;
-    type StaticVersion = RelationMap<T>;
-    const SIDE: LocalSide = LocalSide::Domain;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        <RelationMap<T> as RelationInterface>::get_distinguishing_name(self)
-    }
-
-    fn get_data(&self) -> DBResult<&RelationData> {
-        <RelationMap<T> as RelationInterface>::get_data(self)
-    }
-}
-
 impl<T: Entity> Datum for RelationMap<T> {
     fn sql_type() -> &'static str {
         unreachable!()
@@ -201,36 +164,6 @@ impl<R: Relation> std::fmt::Debug for RelationDomain<R> {
     }
 }
 
-impl<R: Relation> RelationInterface for RelationDomain<R> {
-    type RemoteEntity = R::Range;
-    type StaticVersion = Self;
-    const SIDE: LocalSide = LocalSide::Domain;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        Ok(R::NAME)
-    }
-
-    fn get_data(&self) -> DBResult<&RelationData> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError("Reading from unassigned RelationDomain"))
-    }
-}
-
-impl<'l, R: Relation> RelationInterface for &'l RelationDomain<R> {
-    type RemoteEntity = R::Range;
-    type StaticVersion = RelationDomain<R>;
-    const SIDE: LocalSide = LocalSide::Domain;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        <RelationDomain<R> as RelationInterface>::get_distinguishing_name(self)
-    }
-
-    fn get_data(&self) -> DBResult<&RelationData> {
-        <RelationDomain<R> as RelationInterface>::get_data(self)
-    }
-}
-
 impl<R: Relation> Datum for RelationDomain<R> {
     fn sql_type() -> &'static str {
         unreachable!()
@@ -313,36 +246,6 @@ impl<R: Relation> std::fmt::Debug for RelationRange<R> {
     }
 }
 
-impl<R: Relation> RelationInterface for RelationRange<R> {
-    type RemoteEntity = R::Domain;
-    type StaticVersion = Self;
-    const SIDE: LocalSide = LocalSide::Range;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        Ok(R::NAME)
-    }
-
-    fn get_data(&self) -> DBResult<&RelationData> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError("Reading from unassigned RelationRange"))
-    }
-}
-
-impl<'l, R: Relation> RelationInterface for &'l RelationRange<R> {
-    type RemoteEntity = R::Domain;
-    type StaticVersion = RelationRange<R>;
-    const SIDE: LocalSide = LocalSide::Range;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        <RelationRange<R> as RelationInterface>::get_distinguishing_name(self)
-    }
-
-    fn get_data(&self) -> DBResult<&RelationData> {
-        <RelationRange<R> as RelationInterface>::get_data(self)
-    }
-}
-
 impl<R: Relation> Datum for RelationRange<R> {
     fn sql_type() -> &'static str {
         unreachable!()

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

@@ -7,6 +7,7 @@ pub fn open_test_db_helper<DB: Database>(identifier: &'static str) -> DB {
     );
     let path = path.replace("::", "_");
     let _ = std::fs::remove_file(path.as_str());
+    let pool = microrm::ConnectionPool::new(path).expect("couldn't connect to database");
     DB::open_path(path).expect("couldn't open database file")
 }
 

+ 1 - 0
microrm/tests/conflicts.rs

@@ -9,6 +9,7 @@ struct Meta {}
 
 #[derive(Database)]
 struct MetaConflictDB {
+    #[allow(unused)]
     pub meta_list: microrm::IDMap<Meta>,
 }
 

+ 3 - 3
microrm/tests/manual_construction.rs

@@ -1,7 +1,7 @@
 // simple hand-built database example
 #![allow(unused)]
 
-use microrm::db::{Connection, StatementContext, StatementRow};
+use microrm::db::{ConnectionPool, StatementContext, StatementRow};
 use microrm::schema::datum::{ConcreteDatum, Datum};
 use microrm::schema::entity::{Entity, EntityID, EntityPart, EntityPartVisitor, EntityVisitor};
 use microrm::schema::{Database, DatabaseItem, DatabaseItemVisitor, IDMap};
@@ -125,12 +125,12 @@ struct SimpleDatabase {
 }
 
 impl Database for SimpleDatabase {
-    fn build(conn: Connection) -> Self
+    fn build() -> Self
     where
         Self: Sized,
     {
         Self {
-            strings: IDMap::build(conn),
+            strings: IDMap::build(),
         }
     }