Sfoglia il codice sorgente

Add some documentation and remove some redundant code.

Kestrel 7 mesi fa
parent
commit
a69204fe9e

+ 0 - 4
microrm-macros/src/database.rs

@@ -48,15 +48,11 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
             db_ident,
             field.0.to_string().to_case(Case::UpperCamel)
         );
-        let item_base_name = &field.0.to_string();
         let item_type = &field.1;
 
         quote! {
             struct #item_combined_name;
             impl ::microrm::schema::DatabaseItem for #item_combined_name {
-                fn item_key() -> &'static str { #item_base_name }
-                fn dependency_keys() -> &'static [&'static str] { &[] } // TODO
-
                 fn accept_entity_visitor(visitor: &mut impl ::microrm::schema::entity::EntityVisitor) {
                     <#item_type as ::microrm::schema::DatabaseSpec>::accept_entity_visitor(visitor);
                 }

+ 0 - 6
microrm-macros/src/entity.rs

@@ -85,8 +85,6 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
         let part_base_name = &part.0.to_string();
         let part_type = &part.1;
 
-        let placeholder = format!("${}_{}", entity_ident, part_base_name);
-
         let unique = is_unique(&part.2);
 
         let doc = extract_doc_comment(&part.2);
@@ -100,9 +98,6 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
                 fn part_name() -> &'static str {
                     #part_base_name
                 }
-                fn placeholder() -> &'static str {
-                    #placeholder
-                }
                 fn unique() -> bool {
                     #unique
                 }
@@ -213,7 +208,6 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
 
             fn unique() -> bool { false }
             fn part_name() -> &'static str { "id" }
-            fn placeholder() -> &'static str { "TODO" }
 
             fn desc() -> Option<&'static str> { None }
 

+ 0 - 2
microrm/Cargo.toml

@@ -28,8 +28,6 @@ time = "0.3"
 microrm-macros = { path = "../microrm-macros", version = "0.4.0-rc.1" }
 log = "0.4.17"
 
-topological-sort = { version = "0.2" }
-
 clap = { version = "4", optional = true }
 
 [dev-dependencies]

+ 18 - 0
microrm/src/db.rs

@@ -65,6 +65,7 @@ impl PreparedKey for std::any::TypeId {
 pub struct Connection(Arc<Mutex<ConnectionData>>);
 
 impl Connection {
+    /// Establish a new connection to a sqlite database object. Note that this type carries no schema information, unlike [`Database`](../schema/traits.Database.html).
     pub fn new(url: &str) -> Result<Self, Error> {
         let db_ptr = unsafe {
             let url = CString::new(url)?;
@@ -97,6 +98,7 @@ impl Connection {
         }))))
     }
 
+    /// 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.lock()?;
 
@@ -312,17 +314,20 @@ mod test {
     }
 }
 
+/// A table row that an `Entity` can be read out of.
 pub struct StatementRow<'a> {
     stmt: &'a Statement,
     _ctx: Option<StatementContext<'a>>,
 }
 
 impl<'a> StatementRow<'a> {
+    /// Read a single [`Readable`] out of the database row.
     pub fn read<T: Readable>(&self, index: i32) -> DBResult<T> {
         T::read_from(self, index)
     }
 }
 
+/// A query context.
 pub struct StatementContext<'a> {
     stmt: &'a Statement,
     owned_strings: Vec<Pin<String>>,
@@ -330,10 +335,15 @@ pub struct StatementContext<'a> {
 }
 
 impl<'a> StatementContext<'a> {
+    /// Bind data to the context at a given index.
+    ///
+    /// Remember that bind indices start at 1!
     pub fn bind<B: Bindable>(&self, index: i32, bindable: B) -> DBResult<()> {
         bindable.bind(self, index)
     }
 
+    /// Transfer ownership of a string to the statement context. For transient strings, this is required in order to have
+    /// the string outlive the query.
     pub fn transfer(&mut self, s: Pin<String>) {
         self.owned_strings.push(s);
     }
@@ -416,7 +426,9 @@ impl<'a> Drop for StatementContext<'a> {
     }
 }
 
+/// Data that can be bound into a [`StatementContext`].
 pub trait Bindable {
+    /// Bind data into the [`StatementContext`].
     fn bind<'ctx, 'data: 'ctx>(
         &'data self,
         ctx: &StatementContext<'ctx>,
@@ -533,10 +545,16 @@ impl<'a> Bindable for &'a [u8] {
     }
 }
 
+/// Data that can be read from a [`StatementRow`].
 pub trait Readable: Sized {
+    /// Read an instance of `Self` from a [`StatementRow`].
+    ///
+    /// Remember that read indices start at 0!
     fn read_from(sr: &StatementRow<'_>, index: i32) -> DBResult<Self>;
 }
 
+/// Readable that can be used to pre-determine if a value is NULL, i.e. for `Option::None`
+/// checking.
 pub struct IsNull(pub bool);
 
 // NULL-checker

+ 8 - 0
microrm/src/lib.rs

@@ -21,9 +21,12 @@
 //! currently 1.75. Don't be scared off by the web of traits in the `schema` module --- you should
 //! never need to interact with any of them!
 
+#![warn(missing_docs)]
+
 // to make the proc_macros work inside the microrm crate; needed for tests and the metaschema.
 extern crate self as microrm;
 
+/// SQLite database interaction functions.
 pub mod db;
 mod query;
 pub mod schema;
@@ -31,6 +34,7 @@ pub mod schema;
 #[cfg(feature = "clap")]
 pub mod cli;
 
+/// Module prelude with commonly-used traits that shouldn't have overlap with other crates.
 pub mod prelude {
     pub use crate::query::{AssocInterface, Insertable, Queryable};
     pub use crate::schema::{AssocMap, Database, IDMap};
@@ -61,8 +65,11 @@ pub enum Error {
     ConstraintViolation(&'static str),
     /// Sqlite internal error that has not been translated into a more specific error.
     Sqlite {
+        /// SQLite internal error code.
         code: i32,
+        /// SQLite's error message for the error.
         msg: String,
+        /// The SQL that triggered the error, if known.
         sql: Option<String>,
     },
     /// Something strange happened with JSON serialization/deserialization.
@@ -97,4 +104,5 @@ impl From<std::str::Utf8Error> for Error {
 
 impl std::error::Error for Error {}
 
+/// Shorthand alias.
 pub type DBResult<T> = Result<T, Error>;

+ 48 - 52
microrm/src/schema.rs

@@ -1,10 +1,10 @@
-//! Schema specification
+//! Schema specification types.
 //!
-//! Terminology used:
-//! - domain: one side of an association/relation, the "pointed-from" side for one-sided relations
-//! - range: one side of an association/relation, the "pointed-to" side for one-sided relations
-//! - local: the current side of an association
-//! - remote: the opposite side of an association
+//! The following terminology used in some types in this module:
+//! - *domain*: one side of an association/relation, the "pointed-from" side for one-sided relations
+//! - *range*: one side of an association/relation, the "pointed-to" side for one-sided relations
+//! - *local*: the current side of an association
+//! - *remote*: the opposite side of an association
 
 use query::Queryable;
 
@@ -16,9 +16,12 @@ use crate::{
 };
 use crate::{DBResult, Error};
 
-use self::datum::{ConcreteDatum, DatumDiscriminatorRef};
+use self::{datum::{ConcreteDatum, DatumDiscriminatorRef}, entity::EntityPartList};
 
+/// Types related to datums, or struct fields.
 pub mod datum;
+/// Types related to entities, or structs that can be serialized into/deserialized from the
+/// database.
 pub mod entity;
 
 mod build;
@@ -47,14 +50,17 @@ impl<T: Entity> Stored<T> {
         }
     }
 
+    /// Retrieve the entity ID of the stored entity.
     pub fn id(&self) -> T::ID {
         self.id
     }
 
+    /// Consume the `Stored<>` wrapper and return the wrapped data.
     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::update_entity(&self.db, self)?;
@@ -112,7 +118,11 @@ impl<T: Entity> PartialEq for Stored<T> {
 /// define the relation. Can be restricted to be injective if this is desired. Doing so will incur
 /// a small runtime cost, since an extra index must be maintained.
 pub trait Relation: 'static {
+    /// The domain of the relation, aka the "pointed-from" type. This is interchangeable with
+    /// `Range` unless `INJECTIVE` is set to true.
     type Domain: Entity;
+    /// The range of the relation, aka the "pointed-to" type. This is interchangeable with
+    /// `Domain` unless `INJECTIVE` is set to true.
     type Range: Entity;
 
     /// If true, then each Range-type entity can only be referred to by a single Domain-type
@@ -120,12 +130,17 @@ pub trait Relation: 'static {
     /// invertible for a two-way map.
     const INJECTIVE: bool = false;
 
+    /// A unique, human-readable name for the relation, which should be short and not include
+    /// spaces.
     const NAME: &'static str;
 }
 
+/// Enumeration used to represent which side of a relation an [`AssocInterface`] trait implementation is representing.
 #[derive(Debug)]
 pub enum LocalSide {
+    /// The side of the relation that the [`AssocInterface`] represents is the `Domain` side.
     Domain,
+    /// The side of the relation that the [`AssocInterface`] represents is the `Range` side.
     Range,
 }
 
@@ -166,7 +181,10 @@ impl<T: Entity> std::fmt::Debug for AssocMap<T> {
 
 impl<T: Entity> Default for AssocMap<T> {
     fn default() -> Self {
-        Self::empty()
+        Self {
+            data: None,
+            _ghost: Default::default(),
+        }
     }
 }
 
@@ -190,15 +208,6 @@ impl<T: Entity> AssocInterface for AssocMap<T> {
     }
 }
 
-impl<T: Entity> AssocMap<T> {
-    pub fn empty() -> Self {
-        Self {
-            data: None,
-            _ghost: Default::default(),
-        }
-    }
-}
-
 impl<T: Entity> Datum for AssocMap<T> {
     fn sql_type() -> &'static str {
         unreachable!()
@@ -265,7 +274,10 @@ impl<R: Relation> Clone for AssocDomain<R> {
 
 impl<R: Relation> Default for AssocDomain<R> {
     fn default() -> Self {
-        Self::empty()
+        Self {
+            data: None,
+            _ghost: Default::default(),
+        }
     }
 }
 
@@ -278,15 +290,6 @@ impl<R: Relation> std::fmt::Debug for AssocDomain<R> {
     }
 }
 
-impl<R: Relation> AssocDomain<R> {
-    pub fn empty() -> Self {
-        Self {
-            data: None,
-            _ghost: Default::default(),
-        }
-    }
-}
-
 impl<R: Relation> AssocInterface for AssocDomain<R> {
     type RemoteEntity = R::Range;
     const SIDE: LocalSide = LocalSide::Domain;
@@ -368,7 +371,10 @@ impl<R: Relation> Clone for AssocRange<R> {
 
 impl<R: Relation> Default for AssocRange<R> {
     fn default() -> Self {
-        Self::empty()
+        Self {
+            data: None,
+            _ghost: Default::default(),
+        }
     }
 }
 
@@ -381,15 +387,6 @@ impl<R: Relation> std::fmt::Debug for AssocRange<R> {
     }
 }
 
-impl<R: Relation> AssocRange<R> {
-    pub fn empty() -> Self {
-        Self {
-            data: None,
-            _ghost: Default::default(),
-        }
-    }
-}
-
 impl<R: Relation> AssocInterface for AssocRange<R> {
     type RemoteEntity = R::Domain;
     const SIDE: LocalSide = LocalSide::Range;
@@ -557,6 +554,7 @@ pub struct IDMap<T: Entity> {
 }
 
 impl<T: Entity> IDMap<T> {
+    /// Construct a non-empty instance of an `IDMap`.
     pub fn build(db: Connection) -> Self {
         Self {
             conn: db,
@@ -591,36 +589,30 @@ impl<T: Entity> Insertable<T> for IDMap<T> {
     }
 }
 
-pub struct Index<T: Entity, Key: Datum> {
+/// Search index definition.
+pub struct Index<T: Entity, Keys: EntityPartList> {
     _conn: Connection,
-    _ghost: std::marker::PhantomData<(T, Key)>,
+    _ghost: std::marker::PhantomData<(T, Keys)>,
 }
 
 /// Represents a single root-level table or index in a database.
 pub trait DatabaseItem {
-    fn item_key() -> &'static str;
-    fn dependency_keys() -> &'static [&'static str];
-
-    fn is_index() -> bool {
-        false
-    }
-    fn index_over() -> &'static str {
-        unreachable!()
-    }
-    fn index_columns() -> &'static [&'static str] {
-        unreachable!()
-    }
-
+    /// Accept an entity visitor for entity discovery.
     fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
 }
 
+/// Visitor trait for iterating across the types in a [`DatabaseSpec`].
 pub trait DatabaseItemVisitor {
+    ///
     fn visit<DI: DatabaseItem>(&mut self)
     where
         Self: Sized;
 }
 
+/// Trait representing a type that can be used as a field in a type implementing [`Database`] via
+/// the derivation macro.
 pub trait DatabaseSpec {
+    /// Accept an entity visitor.
     fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
 }
 
@@ -632,6 +624,9 @@ impl<T: Entity> DatabaseSpec for IDMap<T> {
 
 /// 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.
     fn open_path<U: AsRef<str>>(uri: U) -> DBResult<Self>
     where
         Self: Sized,
@@ -659,6 +654,7 @@ pub trait Database {
     where
         Self: Sized;
 
+    /// Accept a visitor for iteration.
     fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor)
     where
         Self: Sized;

+ 5 - 36
microrm/src/schema/build.rs

@@ -1,5 +1,3 @@
-use topological_sort::TopologicalSort;
-
 use crate::{
     prelude::*,
     schema::{
@@ -22,7 +20,6 @@ struct TableInfo {
     table_name: String,
     columns: Vec<ColumnInfo>,
     constraints: Vec<String>,
-    dependencies: Vec<String>,
 }
 
 impl TableInfo {
@@ -31,7 +28,6 @@ impl TableInfo {
             table_name: name,
             columns: vec![],
             constraints: vec![],
-            dependencies: vec![],
         }
     }
 
@@ -162,8 +158,6 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                     range_name,
                 } => {
                     let mut assoc_table = TableInfo::new(assoc_table_name.clone());
-                    assoc_table.dependencies.push(table_name.clone());
-                    assoc_table.dependencies.push(range_name.to_string());
 
                     assoc_table.columns.push(ColumnInfo {
                         name: "domain",
@@ -190,8 +184,6 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                     domain_name,
                 } => {
                     let mut assoc_table = TableInfo::new(assoc_table_name.clone());
-                    assoc_table.dependencies.push(table_name.clone());
-                    assoc_table.dependencies.push(domain_name.to_string());
 
                     assoc_table.columns.push(ColumnInfo {
                         name: "domain",
@@ -229,15 +221,6 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
         tables.insert(table_name, table);
     }
 
-    let mut tsort: TopologicalSort<&str> = TopologicalSort::new();
-    for table in tables.values() {
-        tsort.insert(table.table_name.as_str());
-
-        for dep in table.dependencies.iter() {
-            tsort.add_dependency(dep.as_str(), table.table_name.as_str());
-        }
-    }
-
     // this must be a stable hash function, so we very explicitly want to use a SipHasher with
     // known parameters
     #[allow(deprecated)]
@@ -246,26 +229,12 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
 
     let mut queries = vec![];
 
-    loop {
-        let mut table_list = tsort.pop_all();
-        if table_list.is_empty() {
-            break;
-        }
-        // bring into stable ordering
-        table_list.sort();
-
-        for table_name in table_list.into_iter() {
-            let table = tables.get(table_name).unwrap();
-            let create_sql = table.build_creation_query();
-
-            table_name.hash(&mut signature_hasher);
-            create_sql.hash(&mut signature_hasher);
-            queries.push(create_sql);
-        }
-    }
+    for (table_name, table) in tables {
+        let create_sql = table.build_creation_query();
 
-    if !tsort.is_empty() {
-        panic!("Cycle detected in dependency keys!")
+        table_name.hash(&mut signature_hasher);
+        create_sql.hash(&mut signature_hasher);
+        queries.push(create_sql);
     }
 
     // TODO: generate index schemas here

+ 8 - 1
microrm/src/schema/entity.rs

@@ -12,12 +12,14 @@ pub(crate) mod helpers;
 
 /// Integral identifier for an entity.
 pub trait EntityID: 'static + PartialEq + Hash + PartialOrd + Debug + Copy + ConcreteDatum {
+    /// The entity that this ID is representing.
     type Entity: Entity<ID = Self>;
 
     /// Construct from a raw integer.
     ///
     /// Do not use this unless you are _very_ certain it is what you want.
     fn from_raw(id: i64) -> Self;
+    /// Produces a raw integer from an ID. Of dubious use.
     fn into_raw(self) -> i64;
 }
 
@@ -27,14 +29,19 @@ pub trait EntityID: 'static + PartialEq + Hash + PartialOrd + Debug + Copy + Con
 
 /// A single data field in an Entity, automatically declared and derived as part of `#[derive(Entity)]`.
 pub trait EntityPart: Default + Clone + 'static {
+    /// The type of the data field.
     type Datum: ConcreteDatum;
+    /// The type of the struct this field is a member of.
     type Entity: Entity;
 
+    /// String representing the field name.
     fn part_name() -> &'static str;
-    fn placeholder() -> &'static str;
+    /// Whether this field has a #[unique] attribute or not.
     fn unique() -> bool;
+    /// Documentation comment for this field.
     fn desc() -> Option<&'static str>;
 
+    /// Accessor function: given an instance of the outer struct, return this field's value in that instance.
     fn get_datum(from: &Self::Entity) -> &Self::Datum;
 }
 

+ 0 - 3
microrm/src/schema/entity/part_list.rs

@@ -55,9 +55,6 @@ impl<E: Entity> EntityPart for MarkerPart<E> {
     fn get_datum(_from: &Self::Entity) -> &Self::Datum {
         unreachable!()
     }
-    fn placeholder() -> &'static str {
-        "MarkerPart"
-    }
 }
 
 /// Empty list of entity parts. Since [`EntityPartList`] requires an associated [`Entity`], a

+ 3 - 16
microrm/src/schema/tests.rs

@@ -73,9 +73,6 @@ mod manual_test_db {
         fn part_name() -> &'static str {
             "id"
         }
-        fn placeholder() -> &'static str {
-            "simple_entity_id"
-        }
         fn desc() -> Option<&'static str> {
             None
         }
@@ -93,9 +90,6 @@ mod manual_test_db {
         fn part_name() -> &'static str {
             "name"
         }
-        fn placeholder() -> &'static str {
-            "simple_entity_name"
-        }
         fn unique() -> bool {
             true
         }
@@ -151,13 +145,6 @@ mod manual_test_db {
         {
             struct SimpleDatabaseStringsItem;
             impl DatabaseItem for SimpleDatabaseStringsItem {
-                fn item_key() -> &'static str {
-                    "strings"
-                }
-                fn dependency_keys() -> &'static [&'static str] {
-                    &[]
-                }
-
                 fn accept_entity_visitor(visitor: &mut impl EntityVisitor) {
                     visitor.visit::<SimpleEntity>();
                 }
@@ -240,7 +227,7 @@ mod derive_tests {
         db.people
             .insert(Person {
                 name: name_string.clone(),
-                roles: AssocMap::empty(),
+                roles: AssocMap::default(),
             })
             .expect("failed to insert");
 
@@ -256,7 +243,7 @@ mod derive_tests {
         db.people
             .insert(Person {
                 name: name_string.clone(),
-                roles: AssocMap::empty(),
+                roles: AssocMap::default(),
             })
             .expect("couldn't insert test person");
 
@@ -284,7 +271,7 @@ mod derive_tests {
         db.people
             .insert(Person {
                 name: name_string.clone(),
-                roles: AssocMap::empty(),
+                roles: AssocMap::default(),
             })
             .expect("couldn't insert test person");